diff --git a/deploy/nginx/Dockerfile b/deploy/nginx/Dockerfile new file mode 100644 index 0000000..b8e1291 --- /dev/null +++ b/deploy/nginx/Dockerfile @@ -0,0 +1,10 @@ +# Edge nginx image: bake the TLS template so that template changes produce a +# new image and `docker compose up -d --build` recreates the container. +# (With a bind-mounted template, envsubst renders only at container start and +# compose never recreates on file-content changes — forcing --force-recreate.) +# +# Baking exactly ONE template: rendering both would duplicate `upstream api`. +# The VPS override (deploy/vps/) shadows this file by bind-mounting +# contract-check-http.conf.template at the same target path. +FROM nginx:alpine +COPY templates/contract-check.conf.template /etc/nginx/templates/contract-check.conf.template diff --git a/deploy/vps/docker-compose.override.example.yml b/deploy/vps/docker-compose.override.example.yml index 96f4008..5ce9d5e 100644 --- a/deploy/vps/docker-compose.override.example.yml +++ b/deploy/vps/docker-compose.override.example.yml @@ -11,6 +11,7 @@ services: ports: !override - "127.0.0.1:8081:80" volumes: !override - # Same target path as the base file's TLS template bind — overrides it, - # so only ONE template is rendered (both define `upstream api`). + # Shadows the TLS template baked into the image (deploy/nginx/Dockerfile) + # at the same target path, so only ONE template is rendered (both define + # `upstream api`). HTTP-only: the host front.conf terminates TLS. - ./deploy/nginx/templates/contract-check-http.conf.template:/etc/nginx/templates/contract-check.conf.template:ro diff --git a/docker-compose.yml b/docker-compose.yml index bd527cc..1130543 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -299,16 +299,22 @@ services: # /api/v1/*, /admin/*, /metrics, /healthz, /readyz to the api service. nginx: profiles: ["edge"] - image: nginx:alpine + # The conf template is BAKED into this image (deploy/nginx/Dockerfile), so + # template edits → new image → `up -d --build` recreates and re-renders it. + # The VPS override (deploy/vps/) swaps in contract-check-http.conf.template + # via a bind mount at the same target path. + build: + context: ./deploy/nginx + dockerfile: Dockerfile container_name: contract_check-nginx restart: unless-stopped + depends_on: + api: + condition: service_healthy ports: - "80:80" - "443:443" volumes: - # Mount exactly ONE template — rendering both would duplicate `upstream api`. - # The VPS override (deploy/vps/) swaps in contract-check-http.conf.template here. - - ./deploy/nginx/templates/contract-check.conf.template:/etc/nginx/templates/contract-check.conf.template:ro - certbot-data:/etc/letsencrypt:ro - certbot-webroot:/var/www/certbot:ro environment: diff --git a/src/contract_check/core/analysis/ocr.py b/src/contract_check/core/analysis/ocr.py index 0a4919a..4a2534c 100644 --- a/src/contract_check/core/analysis/ocr.py +++ b/src/contract_check/core/analysis/ocr.py @@ -13,7 +13,6 @@ from __future__ import annotations from pathlib import Path from ..logging import get_logger -from .extractor import ExtractionError log = get_logger(__name__) @@ -57,6 +56,11 @@ def ocr_pdf(path: str | Path, *, lang: str = "rus+eng") -> str: stripped = "\n".join(parts).strip() if len(stripped) < 100: + # Imported lazily: `analysis.extractor` imports `extraction.formats`, + # whose package init used to import this module — a module-level + # import here would close a circular-import chain. + from .extractor import ExtractionError + raise ExtractionError( f"OCR тоже дал мало текста ({len(stripped)} симв.). " "Файл, видимо, не содержит распознаваемого текста." diff --git a/src/contract_check/core/extraction/__init__.py b/src/contract_check/core/extraction/__init__.py index 1339bbb..12e8a24 100644 --- a/src/contract_check/core/extraction/__init__.py +++ b/src/contract_check/core/extraction/__init__.py @@ -7,7 +7,6 @@ it in the factory — no domain/pipeline changes. from __future__ import annotations -from ..analysis.ocr import OCRError as _OCRError # re-export for classify() from .adapters.docx_mammoth import MammothDocxExtractor from .adapters.ocr_tesseract import TesseractOcrExtractor from .adapters.pdf_pymupdf import PyMuPDFExtractor @@ -41,9 +40,6 @@ __all__ = [ "get_factory", ] -# Re-exported under its canonical name (imported by worker_extract.handler.classify). -OCRError = _OCRError - def extract_document(data: bytes, *, mime: str = "", filename: str = "") -> ExtractedDocument: """High-level entry: pick adapter → extract → OCR fallback for scans.