Установка

Переменные окружения

Полный список настроек APIStend: значения по умолчанию, влияние и последствия неверной настройки.

На этой странице · 9

Большинство настроек читает apps/api/src/env.ts; журнальные — lib/log-buffer.ts и lib/retention.ts, а адрес API для серверной отрисовки лендинга — apps/web/src/lib/api.ts. При запуске из исходников он подхватывает корневой .env через dotenv; в контейнере переменные приходят из docker-compose.yml и .env рядом с ним. Образец со всеми ключами — .env.example.

Обязательных переменных две. Остальные имеют значения по умолчанию, и в .env.example они выписаны только для наглядности.

Обязательные

Без них API не стартует

ПеременнаяНазначениеВ .env.example
DATABASE_URLстрока подключения к PostgreSQLpostgresql://apistend:apistend@localhost:5433/apistend?schema=public
JWT_SECRETподпись сессий кабинета и хеширование ключей APIdev-only-change-me-…

Пустое или отсутствующее значение — это не предупреждение, а остановка на старте с текстом «Не задана переменная окружения …. Скопируйте .env.example в .env».

Про JWT_SECRET

Это не только ключ подписи сессий. Тем же секретом считается HMAC-SHA256 от ключа API: в базе лежит хеш, а не ключ, и поиск ключа при запросе к шлюзу идёт по этому хешу.

Отсюда два следствия, о которых лучше знать заранее:

  • Смена секрета обесценивает все выданные ключи. Хеши в базе перестают совпадать с хешами тех же ключей, и шлюз отвечает как на неизвестный ключ. Ключи придётся выпустить заново.
  • Смена секрета гасит все сессии кабинета. Сессия живёт две недели, но подпись проверяется на каждом запросе.

Поэтому секрет задаётся один раз, при первом развёртывании, и хранится там же, где остальные секреты:

JWT_SECRET=$(openssl rand -base64 32)

Сеть и адреса

ПеременнаяПо умолчаниюЧто задаёт
API_PORT8080порт шлюза
API_HOST127.0.0.1интерфейс шлюза. В контейнере обязателен 0.0.0.0, иначе снаружи порт недоступен
WEB_PORT3100 в .env.exampleпорт кабинета в контейнере; участвует в вычислении WEB_ORIGIN
WEB_ORIGINhttp://localhost:${WEB_PORT}, а без WEB_PORT — http://localhost:3000единственный origin, которому разрешён CORS с cookie. Неверное значение — кабинет не может войти
NEXT_PUBLIC_API_URLhttp://localhost:8080адрес API для браузера. Попадает в код кабинета на этапе сборки
APISTEND_INTERNAL_API_URLзначение NEXT_PUBLIC_API_URLадрес API для серверной отрисовки Next; в compose это http://api:8080
APISTEND_PUBLIC_ORIGINhttp://localhost:${API_PORT}как API называет себя сам: адреса моков в кабинете, адрес WebSocket-туннеля, параметры локального приложения Bitrix24

NEXT_PUBLIC_API_URL в контейнере задаётся аргументом сборки, а не переменной рантайма: значение вкомпилировано в клиентский бандл. Поменяли адрес — пересоберите образ.

APISTEND_PUBLIC_ORIGIN на HTTPS-адресе важен отдельно: из него строится схема туннеля, и http:// даст CLI ws:// вместо wss://.

База и журнал запросов

ПеременнаяПо умолчаниюЧто задаёт
DB_POOL_SIZE12соединений к PostgreSQL на один процесс. Общее число = значение × количество инстансов
APISTEND_LOG_SAMPLE_RPS300порог, выше которого журнал успешных запросов прореживается. Ошибки не прореживаются никогда, доля выборки видна в /health
APISTEND_LOG_RETENTION_DAYS30срок хранения журнала. Уборка идёт раз в час пачками по 5000 строк

Пул больше не значит быстрее: PostgreSQL держит процесс на каждое соединение. Горячий путь шлюза до базы вообще не доходит — ключи лежат в LRU-кеше, логи пишутся пачками.

Management API

ПеременнаяПо умолчаниюЧто задаёт
MGMT_RATE_LIMIT240запросов в минуту на один серверный ключ или одну сессию кабинета
MAX_SANDBOXES_PER_ACCOUNT20сколько песочниц заводится на один аккаунт

Это защита самого APIStend, а не эмуляция лимитов боевого сервиса: 240 в минуту — четыре запроса в секунду. У тяжёлых операций отдельный, более жёсткий счётчик.

Потолок на песочницы стоит рядом с лимитом частоты по той же причине: у каждой песочницы свои ключи, наложения демо-данных и журнал, и скрипт с правом sandboxes:write завёл бы их сотнями, не выйдя за обычную норму запросов.

Серии событий

Потолки на одну серию

ПеременнаяПо умолчаниюЧто задаёт
BURST_MAX_RATE500событий в секунду
BURST_MAX_COUNT100000событий в одной серии
BURST_MAX_CONCURRENT3одновременных серий в одной песочнице

Запрос выше потолка не отклоняется, а обрезается — и об этом сообщается в ответе. Молчаливое усечение читалось бы как «сделано, как просили».

Доставка вебхуков

ПеременнаяПо умолчаниюЧто задаёт
WEBHOOK_ALLOW_PRIVATE_TARGETSпусторазрешать ли доставку на приватные адреса

Почему это отдельная настройка

Публичную доставку выполняет сервер APIStend, то есть запрос по адресу из песочницы он делает от своего имени. Без проверки любой пользователь общего стенда мог бы заставить его сходить во внутреннюю сеть — включая 169.254.169.254 и прочие адреса метаданных, — а серия событий превращает это ещё и в усилитель нагрузки.

Значение читается так:

ЗначениеNODE_ENVПриватные адреса
пустоне productionразрешены
пустоproductionзапрещены
1любойразрешены
0любойзапрещены

Запрещённый адрес не приводит к молчаливой потере события: получатель отклоняется с текстом вида «Адрес 127.0.0.1 внутренний. Для доставки на свою машину используйте локальный получатель и apistend listen».

Проверка не ограничивается литералом адреса: имя резолвится, и домен, указывающий на приватный диапазон, тоже отклоняется. Что она не ловит — перепривязку DNS между проверкой и запросом; ограничение известное и записано в коде, а не забыто.

Для общего сервера

docker-compose.yml ставит WEBHOOK_ALLOW_PRIVATE_TARGETS: '1' — образ работает с NODE_ENV=production, и без этого локальная доставка на своей машине была бы запрещена. Разворачивая стенд, которым пользуетесь не только вы, уберите эту строку.

Прочее

ПеременнаяПо умолчаниюЧто задаёт
NODE_ENVне заданproduction включает лаконичный лог без pino-pretty и запрещает приватные адреса доставки. В образе задан всегда
APISTEND_SEED1сид демо-данных при старте контейнера. Отрабатывает только на пустой базе; 0 отключает вовсе

APISTEND_SEED читает точка входа контейнера. При запуске из исходников сид зовётся руками — pnpm db:seed, — и он пересоздаёт демо-аккаунт всегда, а не только на пустой базе.