Vocab Bloom Hub

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

Один файл .env в корне репозитория используется обоими приложениями:

  • сервер загружает его в самом начале apps/server/src/main.ts (до импорта любой сущности — см. Фиксация драйвера базы);
  • скрипты фронтенда оборачивают Next.js в dotenv -e "${ENV_FILE:-../../.env}".

ENV_FILE называет другой файл — для сборки, которая запускается вне дерева репозитория или держит секреты под /etc: ENV_FILE=/etc/vocab-bloom-hub/.env yarn start. Сервер пишет в лог, какой файл загрузил, и завершается с кодом 1, когда явно названный файл нельзя прочитать (отсутствие файла по умолчанию — только предупреждение: переменные могут прийти из окружения процесса).

Примечание

Переменные, уже заданные в окружении процесса, всегда сильнее файла (dotenv никогда их не перезаписывает).

Важно

Используйте абсолютный путь — относительный сервер разрешает от своего рабочего каталога, скрипты фронтенда — от apps/frontend.

Переменные

Примечание

Учётные данные админа когда-то назывались USERNAME / PASSWORD; теперь читаются только ADMIN_USERNAME / ADMIN_PASSWORD (голый USERNAME конфликтует с переменной, которую большинство ОС выставляет в имя текущего пользователя).

ПеременнаяОбязательнаПо умолчаниюКто читаетОписание
ADMIN_USERNAMEдасерверЛогин админа. Вместе с ADMIN_PASSWORD из него выводятся ключ login proof и секрет подписи JWT (см. authentication.md).
ADMIN_PASSWORDдасерверПароль админа. Сервер отказывается стартовать, если он отсутствует или пуст.
DATABASE_URLв productionSQLite (только dev)серверURL базы; схема выбирает драйвер. postgres://user:pass@host:5432/db (или postgresql://) — Postgres со схемой под управлением миграций (см. database.md). sqlite:<путь> (например sqlite:./my.sqlite, sqlite::memory:) — better-sqlite3 с synchronize; так браузерные e2e-тесты получают изолированную базу. Любая другая схема валит старт. Без переменной в разработке TypeORM использует dev.sqlite в корне репозитория.
DB_POOL_SIZEнет10серверМаксимум соединений в пуле Postgres (max драйвера pg; на SQLite игнорируется — пула нет). Поднимайте, когда метрики пула (vbh_db_pool_connections, см. observability.md) показывают ожидающих клиентов; на managed Postgres держите реплики × DB_POOL_SIZE ниже лимита соединений инстанса, оставляя запас для миграций и ручных сессий. Целое число, не меньше 1; иначе старт падает.
DB_POOL_IDLE_TIMEOUTнет10серверСекунды, которые простаивающее соединение пула Postgres живёт до закрытия (idleTimeoutMillis драйвера pg). 0 держит простаивающие соединения вечно. Целые секунды; иначе старт падает.
SERVER_PORTнет3010сервер, фронтенд, сайтПорт, который слушает API на NestJS. Фронтенд и сайт читают его как запасной адрес своего маршрута перенаправления /api/* (core/apiProxy.ts), когда API_INTERNAL_URL не задан. В docker compose это порт хоста, на котором публикуется API, по умолчанию 3240.
FRONT_PORTнет3000фронтенд, серверПорт админки при запуске через скрипты workspace (yarn front:dev / yarn start:front) — пробрасывается в PORT через next.config.ts, так что покрывает и next dev, и next start. Сервер читает его для CORS по умолчанию в разработке. Docker-образ и примеры systemd/PM2 задают PORT напрямую. В docker compose это порт хоста для админки, по умолчанию 3241.
PORTнет3000 / 3020фронтенд, сайтЧто на самом деле слушают next start и standalone server.js. docker-compose.yml, Dockerfile'ы и примеры systemd/PM2 задают его явно; скрипты workspace выводят его из FRONT_PORT / SITE_PORT, так что нативный запуск обычно не задаёт его руками.
SITE_PORTнет3020сайт, серверПорт сайта (yarn site:dev / yarn start:site; хостовый порт compose-сервиса site). См. deployment/docker.md. В docker compose порт хоста по умолчанию 3242.
NEXT_PUBLIC_SITE_URLнетhttp://localhost:3020сайтПубличный origin сайта — для абсолютных URL его sitemap'ов (sitemap.xml, sitemap-words.xml), robots.txt, canonical / hreflang ссылок и социальных карточек OpenGraph / Twitter (metadataBase). Вшивается при сборке. В Docker это аргумент сборки (docker-compose.build.yml), на скачанный образ не действует: опубликованный образ сайта собран для https://vocab-bloom-hub.com (deployment/docker.md).
CONTENT_ROOTнеткорень репозиториясайтКорень, из которого сайт читает содержимое репозитория при сборке (docs/*.md, README, OpenAPI-документ; apps/site/src/content/repo.ts). По умолчанию два уровня выше приложения сайта — раскладка чекаута, которую Dockerfile тоже воспроизводит, — так что задавать нужно только для сборки вне этой раскладки.
NEXT_PUBLIC_BASE_API_URLнет/apiфронтенд, сайтБазовый URL, по которому браузер ходит в API (админка, playground и поиск сайта). Вшивается при сборке (префикс NEXT_PUBLIC_), поэтому изменение требует пересборки. Относительное значение (по умолчанию) разрешается от origin страницы — «API под этим origin», что обеспечивает reverse proxy. Серверный рендеринг тоже использует его, если не задан API_INTERNAL_URL, и тогда значение должно быть абсолютным.
API_INTERNAL_URLнет— (NEXT_PUBLIC_BASE_API_URL)фронтенд, сайтАдрес API, по которому сами процессы фронтенда и сайта ходят в обход прокси: http://server:3010/api в docker-compose.yml. Используется для серверного рендеринга (страницы админки, страницы слов сайта) и для перенаправления запросов /api/*, пришедших на origin фронтенда или сайта (без reverse proxy впереди); для перенаправления запасной вариант — http://127.0.0.1:<SERVER_PORT>/api. В браузер никогда не отправляется.
CORS_ORIGINSнетhttp://localhost:<FRONT_PORT>, http://localhost:<SITE_PORT>серверСписок разрешённых CORS-origin через запятую, например https://admin.example.com,https://staging.example.com.
TRUST_PROXYнет— (заголовки игнорируются)серверНастройка Express trust proxy: число хопов (1 для одного reverse proxy), loopback, список IP / CIDR или true. Заставляет rate limit и логи брать адрес клиента из X-Forwarded-For. Задавайте только за прокси; см. deployment/reverse-proxy.md.
METRICS_ENABLEDнетfalseсерверОтдаёт метрики Prometheus по METRICS_PATH (процесс, HTTP по шаблону маршрута, тиры поиска, размер словаря, переносы, пул Postgres). Держите эндпоинт вне публичного интернета; см. observability.md.
METRICS_PATHнет/metricsсерверПуть эндпоинта метрик, вне обеих поверхностей API.
PUBLIC_API_ENABLEDнетtrueсерверОтдаёт публичный read-only префикс /api/v1. false заставляет эти маршруты отвечать 404 (экземпляр только для админа). См. api.md.
ADMIN_API_ENABLEDнетtrueсерверОтдаёт админскую поверхность (/api/en, /api/settings, /api/auth). false заставляет эти маршруты отвечать 404 (публичный экземпляр); выключение обеих поверхностей валит старт.
PUBLIC_API_RATE_LIMITнет100/60серверЗапросов с одного IP клиента на весь префикс /api/v1, в виде <запросов>/<секунд>. Пакетный поиск считается одним запросом. Всё остальное валит старт.
PUBLIC_API_CACHE_MAX_AGEнет3600серверСекунды, которые общий кэш (браузер, CDN, reverse proxy) может держать ответ публичного GET: Cache-Control: public, max-age=<значение> на каждом успешном GET /api/v1. 0 отправляет public, no-cache (перепроверка при каждом использовании; ETag делает её 304 без тела). Всё, кроме неотрицательного целого, валит старт. См. api.md.
SUGGESTIONS_RATE_LIMITнет5/3600серверСообщений с одного клиента на POST /api/v1/suggestions (форма Report a mistake на страницах слов), в виде <запросов>/<секунд> — бюджет отдельный от PUBLIC_API_RATE_LIMIT. Всё остальное валит старт. См. api.md.
DICTIONARY_IMPORT_DIRнет— (серверные датасеты выключены)серверПапка, из которой импорт словаря может читать датасеты (zip-архивы или папки датасетов в формате экспорта, на один уровень вглубь), например смонтированный том. Пути в запросах импорта разрешаются только внутри неё. Без переменной вкладка Archive страницы импорта предлагает только загрузку файла, без серверного списка. См. offline-import.md.
DICTIONARY_AUTO_IMPORTнетfalse (true в docker-compose.yml)серверЗагрузить словарь самостоятельно при первом старте: когда в настройках нет записанной версии датасета, сервер импортирует самый новый датасет из DICTIONARY_IMPORT_DIR или, без него, опубликованный датасет с HuggingFace — в фоне, с прогрессом в логе; GET /api/ready тем временем отвечает 503 importing, а после неудачи 503 import_failed (следующий старт повторяет попытку). Записанная версия означает, что ничего не происходит. См. deployment/docker.md.
DICTIONARY_DATASET_VERSIONнет— (движущийся main)серверЗакрепляет автоматический импорт при первом старте за одной ревизией опубликованного датасета: тег версии репозитория HuggingFace (каждая опубликованная ревизия помечена своим manifest.version, см. data.md), ветка или sha коммита. Ручные импорты выбирают ревизию на странице импорта.
LOG_LEVELнетdebug в разработке, иначе logсерверМинимальный уровень лога сервера: verbose / debug / log / warn / error / fatal (принимаются и trace / info из pino). Неизвестные значения откатываются к умолчанию. См. observability.md.
LOG_FORMATнетjson в production, иначе prettyсерверФорма строк лога в stdout: json — один JSON-объект на строку для сборщика логов (request id, метод, путь, статус, длительность; ошибки со стеком) — или pretty для терминала. Всё остальное валит старт. См. observability.md.
ENV_FILEнеткорневой .envобаПуть к файлу окружения, загружаемому вместо .env в корне репозитория (абсолютный путь). Сервер завершается, когда названный файл нельзя прочитать; см. выше.
AUDIT_RETENTION_DAYSнет90серверДни, которые хранится журнал изменений админа (страница History, GET /api/en/audit); более старые строки удаляются на старте и раз в сутки. 0 хранит их вечно. Целые дни; иначе старт падает.
SHUTDOWN_TIMEOUTнет30серверСекунды, которые может занять аккуратная остановка после SIGTERM / SIGINT: listener закрывается, запросы в полёте завершаются, пул базы закрывается. Сверх бюджета сервер пишет forcing exit и выходит с кодом 1 вместо ожидания SIGKILL от менеджера процессов. Целые секунды, не меньше 1; иначе старт падает. См. deployment/README.md.
NODE_ENVнетобаdevelopment включает отладочный лог и формат pretty; production требует postgres:// в DATABASE_URL, выключает Swagger UI на /api, пишет лог в JSON и предупреждает при каждом входе админа по обычному http (cookie помечена secure, когда запрос пришёл по https, в любом режиме). Управление схемой от него не зависит: SQLite всегда синхронизирует схему, Postgres всегда использует миграции.

Проверки при старте

Сервер проверяет конфигурацию до создания Nest (assertRequiredConfig в apps/server/configuration.ts) и завершается с кодом 1 и понятным сообщением, когда:

  • ADMIN_USERNAME или ADMIN_PASSWORD отсутствует или пуст — это защита от тихого fail-open, когда незагруженный .env оставляет учётные данные неопределёнными и пароль хэшируется как строка "undefined". Префикс ADMIN_ выбран намеренно: голый USERNAME ОС обычно выставляет в имя текущего пользователя, и он молча прошёл бы проверку;
  • NODE_ENV=production, а DATABASE_URL не строка подключения postgres:// — production никогда не запускается на SQLite молча;
  • DATABASE_URL задан, но его схема не распознана (postgres://, postgresql:// или sqlite:<путь>) — угадывание драйвера молча меняло бы способ управления схемой (auto-DDL против миграций);
  • ENV_FILE называет файл, который нельзя прочитать, либо SHUTDOWN_TIMEOUT, LOG_FORMAT, AUDIT_RETENTION_DAYS, PUBLIC_API_RATE_LIMIT, PUBLIC_API_CACHE_MAX_AGE, SUGGESTIONS_RATE_LIMIT, DB_POOL_SIZE, DB_POOL_IDLE_TIMEOUT содержат значения, которые не разбираются.

Выбранный драйвер базы пишется в лог при старте: Database: Postgres (DATABASE_URL) или better-sqlite3 (<путь>); на Postgres следующая строка сообщает настройки пула (Database pool: up to N connections …).

Примечание

Изменение переменных пула требует перезапуска сервера — они читаются один раз, при создании пула.

Фиксация драйвера базы

Типы колонок сущностей разрешаются во время импорта внутри декораторов TypeORM (checkIsPostgres()), поэтому выбор драйвера фиксируется при первом вызове и остаётся неизменным на всё время жизни процесса. assertDatabaseDriverConsistent() валит старт, когда сущности были импортированы до загрузки окружения (например, собственная точка входа забыла сначала загрузить .env). Тесты, которым нужны типы SQLite, по той же причине импортируют __tests__/helpers/clearDatabaseUrl.ts раньше любой сущности.

Пример .env

Шаблон — .env.example в корне репозитория: то, что нужно docker compose, и каждая необязательная переменная закомментирована рядом со своим значением по умолчанию. Минимальные файлы для нативного production-запуска и для разработки — в быстром старте README.