Переменные окружения
Один файл .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 | в production | SQLite (только 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.