Руководство по разработке
В среде разработки docker-compose.dev.yml запускает сервисы-зависимости и позволяет независимо запускать backend на Go, веб-фронтенд и docreader. После изменений в коде собирайте и тестируйте соответствующий модуль, а затем проверяйте связанные пути интеграции.
Технологический стек и требования к окружению
Платформа состоит из трёх независимо разрабатываемых процессов:
| Компонент | Каталог | Язык / среда выполнения | Требования к версии (источник) |
|---|---|---|---|
Основной backend app | cmd/server + internal/ | Go | Go 1.26.0 (go 1.26.0 в go.mod), требуется CGO (привязки DuckDB, sqlite-vec) |
Сервис разбора документов docreader | docreader/ | Python + gRPC | Python >= 3.10.18 (requires-python в docreader/pyproject.toml); зависимости управляются через uv (в репозитории есть uv.lock, в Docker — uv sync --locked) |
Фронтенд frontend | frontend/ | Node.js + Vue 3 | Ветка Node 22 (devDependencies содержит @tsconfig/node22, @types/node ^22), Vite 7 + TypeScript ~6.0 + Vue 3.5 + TDesign, версия 0.8.3 |
| CLI | cli/ (отдельный Go-модуль) | Go | Go 1.26 (матрица go: ['1.26'] в .github/workflows/cli.yml) |
Рекомендуемые дополнительные инструменты разработки:
# CLI миграций базы данных (используется scripts/migrate.sh)
go install -tags 'postgres' github.com/golang-migrate/migrate/v4/cmd/migrate@latest
# Проверка кода (вызывается make lint)
# Способ установки см. на https://golangci-lint.run; в корне репозитория есть конфигурация .golangci.yml
# Генерация документации Swagger (вызывается make docs)
make install-swagger # go install github.com/swaggo/swag/cmd/swag@latest
# Управление зависимостями Python
pip install uv # docreader устанавливает зависимости через uv sync
# Docker + Docker Compose (подойдёт как плагин v2, так и отдельный docker-compose; scripts/dev.sh определяет автоматически)Быстрый старт: режим разработки (рекомендуется)
В режиме разработки инфраструктура работает в Docker, а app и frontend — локально. После изменения кода приложения процессы можно просто перезапустить, без пересборки образов зависимостей. Точка входа — scripts/dev.sh; цели dev-* в Makefile инкапсулируют соответствующие операции.
# 1. Подготовьте переменные окружения: dev.sh загрузит .env (обязателен), затем перекроет .env.local (опционально)
cp .env.example .env
# 2. Запустите инфраструктуру (ParadeDB/Postgres + Redis + docreader, по умолчанию ещё Langfuse)
make dev-start # эквивалент ./scripts/dev.sh start
make dev-start DEV_ARGS=--qdrant # подключить дополнительный profile
# 3. В другом терминале: локальный запуск backend (внутри выполняет go run -ldflags=... ./cmd/server)
make dev-app # эквивалент ./scripts/dev.sh app
# 4. Ещё один терминал: локальный запуск фронтенда (cd frontend && npm install && npm run dev)
make dev-frontend # эквивалент ./scripts/dev.sh frontend
# Прочее
make dev-status # статус контейнеров
make dev-logs # логи
make dev-stop # остановка
make dev-restart # перезапускDev-сервер фронтенда слушает 5173 (server.port: 5173 в frontend/vite.config.ts) и проксирует /api, /files и /mcp/ на backend. Цель прокси берётся последовательно из переменных окружения процесса VITE_DEV_PROXY_TARGET, FRONTEND_BACKEND_URL; если обе не заданы или пусты, используется http://localhost:8080. DEV_PROXY_TARGET — внутренняя константа в конфигурационном файле, а не переменная окружения.
При подключении к удалённому backend можно задать значение в .env.local в корне репозитория (замените примерный адрес фактическим адресом backend):
VITE_DEV_PROXY_TARGET=https://kb.example.comЗатем в корне репозитория выполните make dev-frontend; скрипт сначала загрузит .env из корня, затем переопределит его из .env.local и экспортирует переменные для Vite. После изменения цели прокси dev-сервер фронтенда нужно перезапустить.
Если запускать npm-команды напрямую в каталоге frontend/, переменную окружения нужно передавать явно; такой способ не проходит через загрузку файлов окружения из корна репозитория:
cd frontend
VITE_DEV_PROXY_TARGET=https://kb.example.com npm run devvite preview (порт 4173) поднимает сервис на production-сборке и является наиболее близкой к release-образу средой проверки. Он использует те же пути прокси и те же правила выбора цели: после выполнения npm run build в каталоге frontend/ к удалённому backend можно подключиться командой VITE_DEV_PROXY_TARGET=https://kb.example.com npm run preview.
Список сервисов docker-compose.dev.yml
docker-compose.dev.yml содержит только сервисы-зависимости, без app/frontend. Ниже перечислены сервисы, запускаемые по умолчанию и опционально через profile (profile включается параметром dev.sh start):
| Сервис | Образ | Порт (по умолчанию) | Условие запуска |
|---|---|---|---|
postgres | paradedb/paradedb:v0.25.11-pg17 (со встроенным pg_search/BM25) | 5432 | по умолчанию |
redis | redis:7.0-alpine (--requirepass) | 6379 | по умолчанию |
docreader | локальная сборка docker/Dockerfile.docreader | 50051 (gRPC) | по умолчанию |
searxng (+searxng-init) | searxng/searxng:latest | 127.0.0.1:8888 | --searxng / --full (compose profile searxng) |
minio | pgsty/minio:latest | 9000 / консоль 9001 | --minio / --full |
qdrant | qdrant/qdrant:v1.16.2 | 6333 / 6334 | --qdrant / --full |
opensearch | opensearchproject/opensearch:3.3.2 (security отключён, чистый HTTP) | 9200 | profile opensearch / full |
opensearch-dashboards | opensearchproject/opensearch-dashboards:3.3.0 | 5601 | profile opensearch-ui (запускается отдельно по необходимости) |
milvus | milvusdb/milvus:v2.6.11 (standalone, встроенный etcd) | 19530 / 9091 | profile milvus / full |
neo4j | neo4j:latest (плагин APOC) | 7474 / 7687 | --neo4j / --full |
dex | dexidp/dex:latest (тестовый источник идентификации OIDC, конфигурация misc/dex-config.yaml) | 5556 | --dex / --full |
langfuse-web / langfuse-worker / langfuse-clickhouse / langfuse-minio / langfuse-db-init | Самостоятельно размещаемый стек Langfuse v3, использует dev-postgres (отдельная БД langfuse) и redis (DB 1) | web 3000, minio 9100/9101 | --langfuse (в dev.sh включён по умолчанию, отключается --no-langfuse) |
odl-hybrid | локальная сборка docker/Dockerfile.odl-hybrid (PDF-бэкенд Docling) | 5002 | --odl-hybrid (образ большой, по необходимости) |
sandbox | wechatopenai/weknora-sandbox (песочница выполнения скриптов Skills, только build/pull, не постоянно) | - | profile full |
Опциональные параметры dev.sh start: --minio, --qdrant, --neo4j, --dex, --langfuse (включён по умолчанию), --no-langfuse, --odl-hybrid, --full (все опциональные сервисы, кроме odl-hybrid). Передача параметров через Makefile: make dev-start DEV_ARGS=--odl-hybrid.
Локальный запуск docreader отдельно
dev-start по умолчанию запускает docreader в контейнере; если нужно отлаживать Python-код локально:
cd docreader
uv sync # установка зависимостей по uv.lock (в контейнере — uv sync --locked --no-dev)
uv run -m docreader.main # запуск gRPC-сервиса (совпадает с CMD в Dockerfile), слушает DOCREADER_GRPC_PORT (по умолчанию 50051)Многочисленные параметры тонкой настройки docreader (DPI рендеринга PDF, определение сканов, белый список SSRF, gRPC TLS и т. д.) задаются переменными окружения DOCREADER_*; полный список см. в разделе docreader.environment файла docker-compose.dev.yml.
Режим Lite (без внешних зависимостей)
Режим Lite компилирует SQLite (+sqlite-vec) и очередь в памяти в один бинарник; подходит для быстрого знакомства и десктопной версии:
make build-lite # сначала собрать фронтенд в web/, затем CGO-сборка Go (tags: sqlite_fts5); SKIP_FRONTEND=1 пропускает фронтенд
make run-lite # требует .env.lite, собирает и запускает Lite-версию
make package-lite # собрать tarball-дистрибутив (scripts/package-lite.sh)
make package-mac-app # собрать macOS .app (scripts/package-mac-app.sh)Обзор целей Makefile
Перечисленные цели определены в корневом Makefile; make help также выводит справку.
Базовая сборка и запуск
| Цель | Назначение |
|---|---|
build | go build -o WeKnora ./cmd/server |
run | сначала build, затем запуск ./WeKnora |
test | go test -v ./... |
clean | go clean и удаление бинарника |
build-prod | Production-сборка: CGO_ENABLED=1, -ldflags "-w -s" внедряет Version/CommitID/BuildTime/GoVersion (переменные пакета internal/handler) и задаёт protobuf conflictPolicy=warn (во избежание конфликтов proto qdrant/milvus) |
fmt | go fmt ./... |
lint | golangci-lint run |
deps | go mod download |
docs | генерирует документацию Swagger командой swag init (параметры см. в Makefile, включая --overridesFile ./docs/swag.overrides) |
install-swagger | устанавливает CLI swag |
Управление Docker-образами и сервисами
| Цель | Назначение |
|---|---|
docker-build-app | собирает wechatopenai/weknora-app (docker/Dockerfile.app, внедряет информацию о версии из scripts/get_version.sh) |
docker-build-docreader | собирает wechatopenai/weknora-docreader (docker/Dockerfile.docreader) |
docker-build-frontend | многоэтапная сборка wechatopenai/weknora-ui (в builder npm ci + npm run build, предварительная сборка dist на хосте не нужна; автоматически внедряет VITE_FRONTEND_COMMIT из git) |
docker-build-all | все три образа выше |
docker-run | гарантирует наличие .env (при отсутствии копирует из .env.example или создаёт пустой) и выполняет docker-compose up |
docker-stop / docker-restart | docker-compose down / stop -t 60 + up |
start-all / stop-all | scripts/start_all.sh (запуск/остановка всех сервисов одной командой) |
start-ollama / start-docker | start_all.sh --ollama / --docker |
build-images / build-images-app / build-images-docreader / build-images-frontend / clean-images | scripts/build_images.sh собирает/очищает образы из исходников |
check-env / list-containers / pull-images | start_all.sh --check / --list / --pull |
show-platform | показывает uname -m и платформу сборки Docker (автоопределение amd64/arm64) |
clean-db | удаляет три Docker volume weknora_postgres-data / weknora_minio_data / weknora_redis_data (очищает данные) |
Миграции базы данных (подробнее — в главе «База данных и миграции»)
| Цель | Назначение |
|---|---|
migrate-up / migrate-down | scripts/migrate.sh up / down |
migrate-version | просмотр текущей версии миграций |
migrate-create name=xxx | создаёт пару новых файлов миграции |
migrate-force version=N | принудительно задаёт версию (восстановление из dirty state) |
migrate-goto version=N | миграция к указанной версии |
Режим разработки и Lite
| Цель | Назначение |
|---|---|
dev-start / dev-stop / dev-restart / dev-logs / dev-status | scripts/dev.sh start/stop/restart/logs/status (поддерживает передачу параметров profile через DEV_ARGS) |
dev-app | локальный go run ./cmd/server (с версионными ldflags) |
dev-frontend | локальный npm run dev |
build-lite / run-lite / package-lite / package-mac-app | сборка/запуск/упаковка режима Lite (см. 2.3) |
download_spatial | go run cmd/download/duckdb/duckdb.go скачивает расширение DuckDB spatial (для инструмента анализа данных) |
Каталог производителей моделей
| Цель | Назначение |
|---|---|
model-catalog-generate | python3 scripts/model-catalog/generate.py — повторно генерирует метаданные каталога производителей и переопределения протоколов |
model-catalog-check | проверяет актуальность сгенерированных данных и запускает go test ./internal/models/... (CI проверяет так же) |
model-catalog-diff | сравнивает с models.dev и выводит отчёт о различиях метаданных моделей; только для ручного просмотра, файлы не изменяются; параметр VENDOR=deepseek ограничивает одним производителем |
Способ подключения производителей см. в разделе Точки расширения.
Система тестирования
Юнит-тесты Go (основной модуль)
make test # go test -v ./...
# или по пакетам:
go test ./internal/infrastructure/chunker/...
go test -run TestXxx ./internal/application/service/...Тесты основного модуля широко используют in-memory-заглушки go-sqlmock, miniredis и т. п. (см. go.mod); большинство запускается без реальной базы данных. Часть пакетов зависит от CGO (DuckDB/sqlite-vec).
Тесты docreader (Python)
Тесты находятся в docreader/tests/, написаны на стандартной библиотеке unittest (в файлах — unittest.main()), покрывают маршрутизацию разбора, параллелизм, разбор EPUB/Excel/MHTML/PDF, защиту от SSRF и т. д.:
cd docreader
uv sync
uv run python -m unittest discover -s tests -v # все
uv run python -m unittest tests.test_parser_routing # отдельныйТесты CLI и приёмочные тесты
cli/ — отдельный Go-модуль со своим cli/Makefile:
cd cli
make test # go test ./...
make test-coverage # с покрытием
make lint # go vetСквозные контрактные/интеграционные тесты собраны в cli/acceptance/ (см. cli/acceptance/doc.go):
cli/acceptance/contract/— golden-тесты формы вывода envelope JSON + согласованность реестра error.code;cli/acceptance/e2e/— чёрноящичные тесты (в стиле testscript) против реального сервера aihelpme.ru; требуют переменных окружения, указывающих на тестовый сервер; на стороне CI выполняется через.github/workflows/cli-e2e.yml, запускается по необходимости (workflow_dispatchвручную или меткаacceptance-e2eна PR), использует secretsWEKNORA_E2E_HOST/WEKNORA_E2E_TOKEN.
Каталог tests/ и тесты фронтенда
tests/miniprogram/miniprogram.test.js— интеграционные тесты клиента мини-программы (тестовый скрипт на Node), сейчас единственное содержимоеtests/;- Фронтенд:
cd frontend && npm run type-check(vue-tsc) иnpm test(tsx --test, Node test runner).
Стандарты кода и процесс внесения изменений
Стандарты кода Go
Корневой .golangci.yml (формат конфигурации golangci-lint v2):
version: 2
linters-settings:
lll:
line-length: 120
tab-width: 4
linters:
enable:
- lll # контролирует ширину строки (120 столбцов)
- govet
- revive
formatters:
enable:
- gofmt
- gofumptПеред коммитом рекомендуется выполнить:
make fmt && make lint && make testОбратите внимание: стандарт форматирования — gofumpt (строже, чем gofmt), предельная ширина строки 120.
Можно также установить встроенные Git hooks (./scripts/install-git-hooks.sh, устанавливает core.hooksPath на scripts/git-hooks): pre-commit проверяет пробельные символы, автоматически запускает gofmt и, если установлен, golangci-lint; pre-push, как и CI, выполняет gofmt и go vet / go test / go build. Для временного пропуска задайте SKIP_HOOKS=1; чтобы пропустить только тесты в pre-push — HOOK_SKIP_TEST=1.
При добавлении или обновлении сторонних зависимостей, а также при изменении файлов данных, распространяемых с релизом, необходимо синхронно обновлять THIRD_PARTY_NOTICES.md и licenses/ и проводить самопроверку через scripts/check-license-bundle.sh (app.yml выполняет такие же проверки).
CI и процесс внесения изменений
Фактическая конфигурация в .github/:
| Файл | Путь-триггер | Назначение |
|---|---|---|
workflows/app.yml | Go-код корневого модуля, go.mod, config/, migrations/, scripts/, docker/Dockerfile.app, файлы лицензий, данные каталога моделей | Проверки основного модуля: контроль форматирования gofmt (только для коммитов в PR), go vet, go test, go build ./cmd/server; дополнительно проверяет пакет лицензий третьих сторон, результат генерации каталога производителей моделей и тесты Git hooks |
workflows/go-lint.yml / go-lint-cache.yml | PR / main | golangci-lint сообщает только о новых проблемах PR относительно базовой линии слияния; go-lint-cache.yml прогревает кэш на main |
workflows/frontend.yml | frontend/, scripts/build_frontend_dist.sh | Node 24: npm test + npm run type-check + npm run build; дополнительно собирает многоэтапный образ frontend/Dockerfile (без push) и проверяет страницу встраивания и маршрут MCP-прокси внутри образа (scripts/test_embed_nginx.py) |
workflows/docreader.yml | docreader/, testdata/, packages/, соответствующие Dockerfile | uv устанавливает зависимости → compileall → unittest discover docreader/tests; затем поднимает gRPC-сервис docreader и запускает go test ./docreader/client ./docreader/proto |
workflows/mcp-server.yml | mcp-server/ | Матричные тесты Python 3.10–3.13; после слияния в main автоматически публикует в PyPI через Trusted Publishing по номеру версии из pyproject.toml (если версия уже существует, загрузка пропускается; тег не обязателен) |
workflows/cli.yml | cli/ | Матрица трёх платформ ubuntu/macos/windows, Go 1.26, go build + go test -race -coverprofile + go vet + проверка словаря skill wire |
workflows/cli-e2e.yml | вручную / label | Сквозная приёмка CLI (метка acceptance-e2e или ручной запуск, см. 4.3) |
workflows/docker-image.yml | — | Сборка и публикация Docker-образов |
workflows/release-lite.yml | — | Публикация версии Lite |
workflows/anydoc.yml | third_party/anydoc-go/, internal/infrastructure/docparser/ | Сборка и тест внутрипроцессного движка разбора Office |
workflows/dsh-plugin.yml | packages/dsh-weknora/ | Тест и публикация плагина DeepSeek Harness |
pull_request_template.md | — | Шаблон PR |
ISSUE_TEMPLATE/ | — | Шаблоны Issue |
dependabot.yml | — | Бот обновления зависимостей |
Четыре проверки, запускаемые по путям (app / frontend / docreader / mcp-server), покрывают основные модули, но локальный прогон всё равно экономит время. Для фронтенда можно сразу использовать scripts/verify_frontend_pr.sh; он выполняет npm test → npm run type-check → npm run build в том же порядке, что и CI.
Процесс внесения изменений: fork / ветка → локально fmt + lint + test → PR (заполняется по шаблону) → запуск CI по затронутым путям.
Приёмы отладки
Уровни логирования
Логирование реализовано в internal/logger/logger.go (logrus). Уровень управляется переменной окружения LOG_LEVEL, допустимые значения debug / info / warn (warning) / error / fatal; при отсутствии или недопустимом значении по умолчанию используется debug (getLogLevelFromEnv()). LOG_PATH задаёт путь вывода; оба параметра вступают в силу сразу после загрузки .env в main(). На стороне docreader LOG_LEVEL читается так же (пробрасывается в compose).
Каждый запрос несёт X-Request-ID, пронизывающий логи app и docreader (init_logging_request_id в docreader); при диагностике сначала берите request id.
GIN_MODE и Swagger
- При
GIN_MODE=releaseотключается Swagger UI (internal/router/router.go) и меняется безопасное поведение embed channel. При локальной разработке из исходников можно задатьdebug; если в Docker Compose переменная не задана, по умолчанию используетсяrelease. - Swagger предоставляется backend app. Адрес по умолчанию —
http://localhost:8080/swagger/index.html; если в Compose изменёнAPP_PORT, используйте соответствующий порт, опубликованный на хосте. Для порта фронтенда (по умолчанию80) и порта dev-сервера Vite (по умолчанию5173) прокси/swagger/не настроен. - Релизный образ уже содержит документацию Swagger;
make docsнужен только при изменении комментариев интерфейсов, когда требуется перегенерировать документацию и собрать backend.
Шаги доступа в Docker Compose
Задайте GIN_MODE=debug в .env проекта, затем пересоздайте контейнер app, чтобы переменная окружения вступила в силу (простой docker compose restart app не обновляет окружение контейнера):
docker compose up -d --no-deps --force-recreate app
docker compose exec app printenv GIN_MODE
docker compose port app 8080Убедитесь, что первая проверка вывела debug, затем обратитесь к backend по отображённому второй проверкой порту. Например, при выводе 0.0.0.0:8080 с этой же машины откройте http://localhost:8080/swagger/index.html; с другой машины замените localhost на адрес машины развёртывания. После завершения диагностики верните GIN_MODE в release и пересоздайте контейнер app.
Пустая страница или ошибка загрузки после открытия
Сначала проверьте, доходит ли запрос до backend (ниже пример с портом backend по умолчанию 8080):
curl -i http://localhost:8080/swagger/index.html
curl -i http://localhost:8080/swagger/doc.jsonВ норме оба возвращают 200: первый — HTML с swagger-ui, второй — JSON с полями swagger и paths.
| Симптом | Направление диагностики |
|---|---|
Код состояния 200, но HTML — обычная страница фронтенда (с <div id="app">), страница пустая или перебрасывает на страницу входа | Запрос попал в SPA-фолбэк фронтенда. Используйте порт, отображённый на backend app; фронтенд входа не добавляет для него прокси /swagger/. |
Backend возвращает 401 или 404, Swagger UI отсутствует | Проверьте порт доступа и фактический GIN_MODE внутри контейнера; в режиме release маршруты Swagger не регистрируются, и несовпавшие запросы могут попасть в middleware аутентификации. |
| Swagger UI открывается, но сообщает об ошибке загрузки определения API | Отдельно проверьте ответ /swagger/doc.json того же backend; при использовании собственного обратного прокси убедитесь, что весь путь /swagger/ и статические ресурсы перенаправляются в app. |
Отладка базы данных и миграций
AUTO_MIGRATE=falseотключает автоматические миграции при запуске;AUTO_RECOVER_DIRTY(включена по умолчанию,falseотключает) управляет автоматическим восстановлением из dirty state (internal/container/container.go). Ошибка миграции только выдаёт предупреждение и не блокирует запуск; обращайте внимание на сообщениеDatabase migration failedв логах запуска.make migrate-versionбыстро показывает версию schema.
Наблюдаемость цепочки LLM (Langfuse)
dev.sh start по умолчанию поднимает самостоятельно размещённый Langfuse (http://localhost:3000). Для локально запущенного через go run app нужно экспортировать:
export LANGFUSE_HOST=http://localhost:3000
export LANGFUSE_PUBLIC_KEY=pk-lf-xxx
export LANGFUSE_SECRET_KEY=sk-lf-xxxПосле этого в UI Langfuse можно просматривать trace вызовов моделей для каждой сессии (spans обработки документов также сохраняются в таблицу knowledge_processing_spans и визуализируются во фронтенде).
pprof
В текущем коде эндпоинт net/http/pprof не встроен (в internal/, cmd/ нет ссылок на pprof). Для профилирования производительности можно временно добавить в cmd/server/main.go import _ "net/http/pprof" и поднять отдельный http.ListenAndServe("localhost:6060", nil) либо использовать go test -bench . -cpuprofile для профилирования конкретного пакета.
Диагностика стратегий разбиения на блоки
chunker предоставляет SplitWithDiagnostics() (internal/infrastructure/chunker/strategy.go), возвращающий выбор цепочки стратегий, причины отклонения каждого tier и профиль документа; вместе с LOG_LEVEL=debug (логи chunker: tier %s rejected) это помогает диагностировать проблемы качества разбиения.
Скрипты обслуживания облачных образов
scripts/cloud-image/ служит для подготовки образов дистрибутива на выделенной одноразовой Linux-машине сборки. При обычном развёртывании используйте Установку и развёртывание; запускать эти скрипты не нужно.
prepare.shскачивает файлы времени выполнения, соответствующиеWEKNORA_REF, вытягивает образы и устанавливает службу systemd. Эта ссылка также задаёт версию образа — убедитесь, что соответствующий тег образа существует. Поставляемый с репозиторием systemd-юнит жёстко использует/opt/WeKnora; изменения только переменной каталога в скрипте недостаточно для переноса места установки.cleanup.shочищает данные, ключи, авторизацию SSH, логи и кэш Docker на машине сборки образов и выключает её; его влияние не ограничивается каталогом aihelpme.ru, поэтому применять его можно только на проверенной выделенной машине сборки.firstboot.shна новом инстансе генерирует ключи, записывает.envи запускает Compose; по завершении служба первого запуска отключается, а скрипт не удаляет себя.
Перед сборкой образа проверьте соответствие скриптов выбранной версии. Перед распространением обязательно проверьте на новом инстансе запуск, работоспособность сервисов и независимость ключей; это описание не означает, что текущие скрипты прошли приёмку развёртывания или публикации на конкретной облачной платформе.
Для диагностики первого запуска смотрите journalctl -u weknora-firstboot, /var/log/weknora-firstboot.log и состояние Compose в /opt/WeKnora. Файл .firstboot.done записывается после генерации ключей, но до запуска Compose; наличие маркера не означает работоспособность сервисов. При неудачном запуске сохраните сгенерированный .env и маркер, устраните причину и снова запустите Compose; удаление маркера и повторная генерация ключей могут привести к рассогласованию конфигурации с уже инициализированной базой данных.
