Архитектура¶
Peter View - три процесса в одной внутренней сети Compose и том с данными. С хоста открыт один порт nginx. Браузер не ходит на FastAPI и LanguageTool напрямую.
Состав стека¶
Браузер
→ nginx (статика и /api, порт на хосте)
→ FastAPI
→ Vale, pymorphy3
→ LanguageTool (отдельный контейнер)
→ модель, если задан адрес
→ том backend-data
| Контейнер | Образ / сборка | Процесс | Память в Compose | С хоста |
|---|---|---|---|---|
frontend |
nginx:alpine, frontend/public |
nginx, порт 80 внутри | без лимита | PROOFREADER_PORT, по умолчанию 3080 |
backend |
python:3.12-slim, Vale 3.9.1, uvicorn | FastAPI app.main:app, порт 8000 |
до 2 ГБ | нет |
languagetool |
erikvl87/languagetool, только ru |
HTTP /v2/check на 8010 |
1.5 ГБ, куча 512 МБ–1 ГБ | нет |
Сеть internal, драйвер bridge. LanguageTool должен пройти healthcheck (до 90 с на старт), затем backend, затем frontend.
Языковая модель в стек не входит. Backend вызывает адрес из «Настроек» или из LLM_*. Пустой адрес: слой модели не вызывается, Vale / морфология / LanguageTool остаются.
Опционально PROOFREADER_CORP_PROXY=true: контейнер proxy-bridge (socat) прокидывает корпоративный прокси хоста, backend ходит в модель через host.docker.internal.
Зачем три процесса¶
LanguageTool - отдельная JVM с длинным стартом. Если держать её в том же процессе, что и API, падение грамматики останавливает вход и гайды. Healthcheck специально растянут: перезапуск каждые десять секунд только мешает разгону.
Данные лежат в томе, не в образе. Обновление контейнера не должно стирать пользователей и гайды.
Интерфейс - статические файлы. Node в производственной среде не нужен. Пункты меню backend отдаёт в /api/config; флаг FEATURE_* меняет меню без пересборки фронта.
Путь запроса из браузера¶
- Браузер открывает
http://хост:3080. nginx отдаётindex.html, скрипты ES-модулей, стили. - Маршруты страниц - hash:
#/check,#/review,#/guides. Обновление страницы не спрашивает сервер о пути. - Все данные идут на тот же origin:
/api/.... nginx проксирует наbackend:8000. Загрузка до 50 МБ, таймаут 600 с. - Проверка и стрим воркеров идут как SSE:
/api/jobs/{id}/streamи устаревший/api/check-url. Для них nginx отключает буферизацию (X-Accel-Buffering: no), иначе карточки появятся пачкой в конце. - Cookie сессии
proofreader_sessionHttpOnly, SameSite=lax. Флаг Secure:PROOFREADER_COOKIE_SECURE. Живые сессии хранятся в памяти backend. Рестарт контейнера разлогинивает всех;users.jsonпри этом остаётся.
CSP на статике: скрипты только 'self'. Исключение: /api/docs и /api/redoc, если PROOFREADER_DOCS=true.
Как устроена проверка в интерфейсе¶
Раздел «Вычитка» не вызывает /api/check. Он ставит задачу:
- Источник разбирается в блоки документа: DOCX / TXT / HTML / MD, либо одна страница по URL (
parse_url). Обход сайта на 200 страниц (/api/check-url) в этом экране не используется. POST /api/jobsс галочками языка, гайда, терминов, идентификатором Style Guide и необязательной инструкцией.- Задача живёт в памяти и дублируется в
jobs.db. Рестарт помечает незавершённые задачи ошибкой «Прервано рестартом сервера». - Браузер подписан на SSE и по окончании забирает отчёт
GET /api/jobs/{id}/report. - Отчёт пишется в историю (
stats.db).
Внутри задачи работает пайплайн PIPELINE_VERSION=v2 (v1 остаётся для отката). Сначала детерминированные движки, затем при наличии модели - проходы LLM. Подробности: как проходит проверка.
Прямые POST /api/check, /api/check-text, /api/check-url остаются в HTTP API: те же Vale / морфология / LanguageTool без очереди jobs. Их вызывает не экран «Вычитка», а отчёт xlsx и краулер.
Backend: модули¶
Код сервера: backend/app/.
| Модуль | Роль |
|---|---|
main.py |
Маршруты HTTP |
auth.py, oidc.py |
Пароль, сессия, вход организации |
features.py |
Флаги разделов, 404 если выключено |
extractors.py |
Текст из файла и HTML |
checker.py |
Параллельно Vale, pymorphy3, LanguageTool |
vale_runner.py |
Vale CLI, JSON |
custom_checks.py |
Морфология: пассив, «Вы», длина предложения, согласование |
lt_client.py |
HTTP к контейнеру LanguageTool |
net_guard.py |
SSRF при загрузке URL |
crawler.py |
Обход сайта, до 200 страниц |
llm/jobs.py |
Очередь проверок, SSE |
llm/pipeline.py, pipeline_v2.py |
Сборка отчёта и проходы модели |
llm/styleguide_store.py |
YAML гайдов на томе |
llm/settings.py |
Адрес модели, перекрывает .env |
llm/repo_store.py |
Репозиторий документов |
llm/watch_store.py, watch_run.py |
Наблюдение за страницами |
backups.py |
tar тома |
audit.py |
Журнал действий администратора |
Интерфейс: frontend/public/js/, по файлу на раздел (check.js, guides.js, users.js, …). Оболочка и вход: app.js. Общие запросы: shared.js. Строки ru/en: frontend/public/i18n/.
Данные¶
Том backend-data монтируется в /app/data. Состав файлов: хранилище. Резервная копия снимает этот каталог в ./backups на хосте.
Сеть и границы¶
Загрузка страницы по ссылке проходит net_guard: схема только http(s), запрещены loopback, link-local (в том числе 169.254.169.254), multicast. Адреса RFC1918 по умолчанию разрешены (PROOFREADER_SSRF_ALLOW_PRIVATE=true), потому что документация часто живёт во внутренней сети. На узле из интернета поставьте false.
Модель и эмбеддинги - исходящие запросы backend по адресу, который задал администратор. На GitHub и на сайт автора проверка ничего не отправляет.
HTTP-группы: справка API. Команды Compose: справка.