[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"docs-infra\u002Fdeploy\u002Fupload":3,"docs-tabs-infra\u002Fdeploy\u002Fupload":6},{"content":4,"lastmod":5},"\n## Загрузить файл\n\n`POST \u002Fv1\u002Finfra\u002Fservers\u002F:id\u002Fupload`\n\nЗаписывает файл на BLACKHOLE-сервер через агент туннеля. Два варианта источника: встроенное base64-содержимое в теле запроса (до 500 МБ) или URL, с которого агент скачает файл сам (до 500 МБ). Поддерживается автоматическая распаковка архивов `tar.gz` \u002F `tar.bz2` \u002F `zip` — формат определяется по сигнатуре файла (magic bytes) или по расширению URL.\n\n## Параметры\n\n| Параметр | В | Тип | Обяз. | Описание |\n|----------|---|-----|:-----:|----------|\n| `id` | path | string (UUID) | да | ID BLACKHOLE-сервера, `status: running`, `blackholeStatus: CONNECTED` |\n\n## Поля запроса (body)\n\n| Поле | Тип | Обяз. | Описание |\n|------|-----|:-----:|----------|\n| `path` | string | **да** | Путь на сервере, 1–500 символов. Для архивов — куда положить загруженный архив до распаковки |\n| `content` | string | да (или `url`) | Base64-содержимое файла. Максимум 500 МБ на тело запроса |\n| `url` | string | да (или `content`) | HTTPS-URL, откуда агент скачает файл. До 500 МБ |\n| `mode` | string | нет | Права файла в восьмеричном формате: `0644`, `0755`. Применяется к файлу по пути `path` |\n| `extract` | boolean | нет | Распаковать архив после загрузки. По умолчанию `false`. Требует `content` или `url` с архивом |\n| `extractTo` | string | нет | Директория для распаковки (при `extract: true`). По умолчанию — директория из `path` |\n\nНужно передать ровно одно из: `content` или `url`.\n\n## Примеры\n\n### curl — личный ключ\n\n```bash\n# Загрузка файла по URL с автоматической распаковкой\ncurl -X POST https:\u002F\u002Fvibecode.bitrix24.tech\u002Fv1\u002Finfra\u002Fservers\u002FSERVER_ID\u002Fupload \\\n  -H \"X-Api-Key: YOUR_API_KEY\" \\\n  -H \"Content-Type: application\u002Fjson\" \\\n  -d '{\n    \"url\": \"https:\u002F\u002Fgithub.com\u002Fuser\u002Frepo\u002Farchive\u002Fmain.tar.gz\",\n    \"path\": \"\u002Fopt\u002Fapp\u002Fsource.tar.gz\",\n    \"extract\": true,\n    \"extractTo\": \"\u002Fopt\u002Fapp\"\n  }'\n\n# Встроенное base64-содержимое — маленький файл (package.json)\nCONTENT=$(echo '{\"name\":\"my-app\",\"version\":\"1.0.0\"}' | base64)\ncurl -X POST https:\u002F\u002Fvibecode.bitrix24.tech\u002Fv1\u002Finfra\u002Fservers\u002FSERVER_ID\u002Fupload \\\n  -H \"X-Api-Key: YOUR_API_KEY\" \\\n  -H \"Content-Type: application\u002Fjson\" \\\n  -d \"{\\\"content\\\":\\\"$CONTENT\\\",\\\"path\\\":\\\"\u002Fopt\u002Fapp\u002Fpackage.json\\\",\\\"mode\\\":\\\"0644\\\"}\"\n```\n\n### curl — OAuth-приложение\n\n```bash\ncurl -X POST https:\u002F\u002Fvibecode.bitrix24.tech\u002Fv1\u002Finfra\u002Fservers\u002FSERVER_ID\u002Fupload \\\n  -H \"X-Api-Key: YOUR_APP_KEY\" \\\n  -H \"Authorization: Bearer USER_SESSION_TOKEN\" \\\n  -H \"Content-Type: application\u002Fjson\" \\\n  -d '{\"url\":\"https:\u002F\u002Fexample.com\u002Fconfig.json\",\"path\":\"\u002Fetc\u002Fmyapp\u002Fconfig.json\"}'\n```\n\n### JavaScript — личный ключ\n\n```javascript\n\u002F\u002F Загрузка архива и распаковка\nconst res = await fetch(\n  `https:\u002F\u002Fvibecode.bitrix24.tech\u002Fv1\u002Finfra\u002Fservers\u002F${serverId}\u002Fupload`,\n  {\n    method: 'POST',\n    headers: {\n      'X-Api-Key': 'YOUR_API_KEY',\n      'Content-Type': 'application\u002Fjson',\n    },\n    body: JSON.stringify({\n      url: 'https:\u002F\u002Fgithub.com\u002Fuser\u002Fapp\u002Farchive\u002Fmain.tar.gz',\n      path: '\u002Fopt\u002Fapp\u002Fsource.tar.gz',\n      extract: true,\n      extractTo: '\u002Fopt\u002Fapp',\n    }),\n  }\n)\nconst { data } = await res.json()\nconsole.log(`Записано ${data.size} байт по пути ${data.path}${data.extracted ? ', распаковано' : ''}`)\n```\n\n### JavaScript — OAuth-приложение\n\n```javascript\n\u002F\u002F Встроенное base64-содержимое — маленький конфиг\nconst content = Buffer.from('NODE_ENV=production\\n').toString('base64')\nawait fetch(\n  `https:\u002F\u002Fvibecode.bitrix24.tech\u002Fv1\u002Finfra\u002Fservers\u002F${serverId}\u002Fupload`,\n  {\n    method: 'POST',\n    headers: {\n      'X-Api-Key': 'YOUR_APP_KEY',\n      'Authorization': 'Bearer USER_SESSION_TOKEN',\n      'Content-Type': 'application\u002Fjson',\n    },\n    body: JSON.stringify({\n      content,\n      path: '\u002Fopt\u002Fapp\u002F.env',\n      mode: '0600',\n    }),\n  }\n)\n```\n\n## Поля ответа\n\n| Поле | Тип | Описание |\n|------|-----|----------|\n| `success` | boolean | `true` при успешной записи |\n| `data.path` | string | Итоговый путь файла на сервере |\n| `data.size` | number | Размер в байтах |\n| `data.extracted` | boolean | `true` если архив был распакован после загрузки |\n\n## Пример ответа\n\n```json\n{\n  \"success\": true,\n  \"data\": {\n    \"path\": \"\u002Fopt\u002Fapp\u002Fsource.tar.gz\",\n    \"size\": 1048576,\n    \"extracted\": true\n  }\n}\n```\n\n## Пример ответа при ошибке\n\n403 — путь запрещён для загрузки:\n\n```json\n{\n  \"success\": false,\n  \"error\": {\n    \"code\": \"UPLOAD_PATH_DENIED\",\n    \"message\": \"Upload to system path is not allowed\"\n  }\n}\n```\n\n## Ошибки\n\n| HTTP | Код | Описание |\n|------|-----|----------|\n| 400 | `VALIDATION_ERROR` | Нарушена схема: одновременно `content` и `url`, либо ни одного, неверный `path`, тело больше 500 МБ |\n| 400 | `NOT_BLACKHOLE` | Сервер в режиме OPEN — Deploy API недоступен |\n| 401 | `MISSING_API_KEY` | Не передан заголовок `X-Api-Key` |\n| 401 | `INVALID_API_KEY` | Неверный или просроченный API-ключ |\n| 403 | `UPLOAD_PATH_DENIED` | Загрузка в системные директории (`\u002Froot\u002F.ssh`, `\u002Fetc\u002Fpasswd`, `\u002Fboot`, …) запрещена |\n| 404 | `NOT_FOUND` | Сервер не существует, удалён или принадлежит другому API-ключу |\n| 409 | `SERVER_NOT_READY` | Сервер не готов к операции: не запущен или туннель не подключён. В ответе — поле `hint` с причиной и следующим шагом. Разбудите сервер или вызовите [`\u002Frepair`](\u002Fdocs\u002Finfra\u002Flifecycle\u002Frepair) и повторите запрос. Случай «числится подключённым, но у Gateway нет живого туннеля» на этом маршруте приходит как `502 TUNNEL_NOT_FOUND` (см. ниже) |\n| 429 | `RATE_LIMITED` | Превышен лимит 10 операций в минуту на сервер |\n| 429 | `DEPLOY_BACKEND_BUSY` | Слишком много одновременных загрузок с телом запроса. Платформа ограничивает число одновременных `\u002Fupload` и `\u002Fdeploy` со встроенным содержимым, чтобы не исчерпать память. Ответ несёт заголовок `Retry-After: 30` — повторите через 30 секунд. Либо передайте архив ссылкой (`url`): для загрузок по ссылке ограничения на одновременность нет |\n| 502 | `UNZIP_PREFLIGHT_FAILED` | Не удалось установить `unzip` на сервере перед распаковкой zip-архива. Чтобы не зависеть от этого шага, используйте архив `.tar.gz` |\n| 500 | код агента | Ошибка записи файла на сервере: нехватка места, права доступа и т. п. Конкретный код приходит от агента в поле `error.code` |\n| 502 | `TUNNEL_NOT_FOUND` \u002F `GATEWAY_UNREACHABLE: …` | Нет живого туннеля до сервера либо Gateway недоступен — вызовите [`\u002Frepair`](\u002Fdocs\u002Finfra\u002Flifecycle\u002Frepair) и повторите. `GATEWAY_UNREACHABLE` несёт детали после двоеточия — сопоставляйте `error.code` по префиксу |\n| 503 | `GATEWAY_TIMEOUT: …` | Gateway не ответил вовремя. Код несёт детали после двоеточия — сравнивайте по префиксу |\n\nПолный список общих ошибок API — [Ошибки](\u002Fdocs\u002Ferrors).\n\n## Известные особенности\n\n- **Автоопределение формата архива при `extract: true`.** Агент определяет формат по сигнатуре файла (magic bytes, функция `detectArchiveExt()`): поддерживает `.tar.gz`, `.zip`, `.tar.bz2`. Формат можно не указывать явно. Для URL — агент также проверяет расширение.\n- **macOS-архивы чистятся автоматически.** Если архив содержит AppleDouble-сайдкары (`._*`) или `.DS_Store`, агент удаляет их после распаковки — это предотвращает ложные срабатывания сканеров вроде Tailwind v4 oxide. При создании архива на macOS советуем использовать `COPYFILE_DISABLE=1 tar -czf ...` чтобы сайдкары вообще не попадали в архив.\n- **Windows PowerShell ZIP — работает на агенте ≥ 1.2.3.** `Compress-Archive` в Windows пишет ZIP с литеральными `\\` в именах файлов. Агент с версии 1.2.3 автоматически нормализует эти пути через шаг `normalize_windows_paths`. Для старых версий агента используйте `tar.gz` (через WSL \u002F Git Bash) либо выкладывайте готовый tar-архив и загружайте через `url`.\n- **Список запрещённых путей.** Агент блокирует загрузку в: `\u002Froot\u002F.ssh\u002F` (защита SSH-ключей), `\u002Fboot`, `\u002Fetc\u002Fshadow`, `\u002Fetc\u002Fpasswd`, системные сервис-файлы. Для приложения используйте `\u002Fopt\u002F\u003Capp>\u002F` или `\u002Fvar\u002Flib\u002F\u003Capp>\u002F`.\n- **`mode` применяется к архиву, не к распакованным файлам.** При `extract: true` права распакованных файлов определяются содержимым архива, а `mode` задаётся только сохранённому архиву. Если нужно изменить права внутри — сделайте через [`\u002Fexec`](.\u002Fexec.md) c `chmod`.\n- **Таймаут 10 минут на загрузку через `url`.** Для 500 МБ должно уложиться. Если источник медленный — предзагрузите архив, положите на свой CDN.\n\n## Смотрите также\n\n- [Полный деплой](.\u002Fdeploy.md) — за один запрос вместо цепочки `upload` + `exec`.\n- [Выполнить команду](.\u002Fexec.md) — после загрузки можно распаковать\u002Fустановить вручную.\n- [Логи сервиса](.\u002Flogs.md) — проверить, что сервис применил новые файлы.\n","2026-06-29",{}]