Files
LegacyHUB/docker-compose.prod.yml
Vadim Malanov d27dd0ffbb feat: align LegacyHUB with TeamHUB platform contract (D2/D4, assets, security)
Close 12 audit-driven platform-compliance gaps on a single branch.

- D4 dispatch: app/integrations/dispatch_client.py participant `legacyhub`,
  emits LegacyhubDocumentIndexed + AssetDerivativeReady after the indexing
  commit (idempotent uuid5), http_inbox route (reindex/tombstone) with
  audit-based dedupe; docs/dispatch-contract.md. Celery+Redis stays intra-module.
- D2 SSO: app/integrations/identity.py validates X-TeamHub-* + role/scope
  mapper; security.py adds trusted-header enforcement (AUTH_REQUIRE_IDENTITY)
  and a scope check on /search; docker-compose.teamhub.yml (external teamhub_net
  + internal db net, api not host-published); RUNBOOK network/firewall section.
- Asset standard: SearchHit/Citation carry asset_id/owner_module; buckets
  renamed teamhub-legacyhub-* (+quarantine/tmp/exports); purge-by-asset_id with
  legal-hold guard (app/indexing/projection.py); OCR-markdown derivative event.
- audit_log model + Alembic 0003 + record_audit on writes (same transaction).
- Secret masking: app/common/json_logger.py recursive mask wired into structlog
  (+ensure_ascii=False); event payloads redacted before persistence.
- Service X-API-Key mandatory on ingest endpoints (defence-in-depth).
- Port: host API 8000->8050 (collision with SalesHUB/MailHUB resolved),
  container still listens on 8000.
- Config: no plaintext secret defaults; fail-loud in non-dev (no value leak).
- Docs drift: README PG 5440, layered-auth note, 5173 removed from CORS;
  ingest/folder gated by ENABLE_FOLDER_INGEST (410 by default).
- ADRs: layers mapping, shared-core extraction, UI locale (RU-first).

Tests: 78 passing (ruff, compileall, pytest, tsc, vite build, compose config).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 11:44:15 +03:00

104 lines
3.6 KiB
YAML

# Production overlay for LegacyHUB.
#
# Usage:
# cp .env.prod.example .env.prod
# $EDITOR .env.prod # rotate every credential
# docker compose \
# -f docker-compose.yml -f docker-compose.prod.yml \
# --env-file .env.prod \
# up -d --build --force-recreate
#
# This overlay narrows the dev-friendly defaults:
# - removes published ports from data services (only api stays public);
# - turns on the OpenSearch security plugin and forces an admin password;
# - requires CORS_ALLOWED_ORIGINS to be set (no localhost fallback);
# - bumps Java + worker concurrency for real workloads;
# - drops the MinIO console.
services:
postgres:
ports: !reset []
environment:
POSTGRES_DB: ${POSTGRES_DB}
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?POSTGRES_PASSWORD must be set}
restart: always
minio:
command: server /data
ports: !reset []
environment:
MINIO_ROOT_USER: ${MINIO_ACCESS_KEY:?MINIO_ACCESS_KEY must be set}
MINIO_ROOT_PASSWORD: ${MINIO_SECRET_KEY:?MINIO_SECRET_KEY must be set}
restart: always
opensearch:
ports: !reset []
environment:
- discovery.type=single-node
- bootstrap.memory_lock=true
- "OPENSEARCH_JAVA_OPTS=-Xms2g -Xmx2g"
- DISABLE_INSTALL_DEMO_CONFIG=true
- plugins.security.disabled=false
- OPENSEARCH_INITIAL_ADMIN_PASSWORD=${OPENSEARCH_ADMIN_PASSWORD:?OPENSEARCH_ADMIN_PASSWORD must be set}
restart: always
qdrant:
ports: !reset []
environment:
QDRANT__SERVICE__API_KEY: ${QDRANT_API_KEY:?QDRANT_API_KEY must be set}
restart: always
redis:
ports: !reset []
restart: always
api:
environment:
<<: &prod-env
POSTGRES_HOST: ${POSTGRES_HOST}
POSTGRES_PORT: ${POSTGRES_PORT}
POSTGRES_DB: ${POSTGRES_DB}
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
MINIO_ENDPOINT: ${MINIO_ENDPOINT}
MINIO_ACCESS_KEY: ${MINIO_ACCESS_KEY}
MINIO_SECRET_KEY: ${MINIO_SECRET_KEY}
MINIO_BUCKET_ORIGINALS: ${MINIO_BUCKET_ORIGINALS}
MINIO_BUCKET_DERIVED: ${MINIO_BUCKET_DERIVED}
MINIO_SECURE: "true"
OPENSEARCH_HOST: ${OPENSEARCH_HOST}
OPENSEARCH_PORT: ${OPENSEARCH_PORT}
OPENSEARCH_USE_SSL: "true"
OPENSEARCH_VERIFY_CERTS: "true"
OPENSEARCH_USER: ${OPENSEARCH_USER}
OPENSEARCH_PASSWORD: ${OPENSEARCH_PASSWORD}
OPENSEARCH_INDEX_CHUNKS: ${OPENSEARCH_INDEX_CHUNKS}
QDRANT_HOST: ${QDRANT_HOST}
QDRANT_PORT: ${QDRANT_PORT}
QDRANT_API_KEY: ${QDRANT_API_KEY}
QDRANT_COLLECTION_CHUNKS: ${QDRANT_COLLECTION_CHUNKS}
REDIS_URL: ${REDIS_URL}
OCR_LANGUAGES: ${OCR_LANGUAGES}
OCR_ENABLED: ${OCR_ENABLED}
DOCLING_OCR_ENABLED: ${DOCLING_OCR_ENABLED}
MAX_DOCUMENT_TIMEOUT_SECONDS: ${MAX_DOCUMENT_TIMEOUT_SECONDS}
EMBEDDING_MODEL: ${EMBEDDING_MODEL}
EMBEDDING_DEVICE: ${EMBEDDING_DEVICE}
RERANKER_MODEL: ${RERANKER_MODEL}
RERANKER_DEVICE: ${RERANKER_DEVICE}
RERANKER_ENABLED: ${RERANKER_ENABLED}
APP_ENV: ${APP_ENV:-prod}
APP_LOG_LEVEL: ${APP_LOG_LEVEL}
APP_INPUT_DIR: /data/input
APP_WORK_DIR: /data/work
CORS_ALLOWED_ORIGINS: ${CORS_ALLOWED_ORIGINS:?CORS_ALLOWED_ORIGINS must be set (no * in production)}
API_KEY: ${API_KEY:?API_KEY must be set in production}
restart: always
worker:
command: ["celery", "-A", "app.workers.celery_app", "worker", "--loglevel=INFO", "--concurrency=4"]
environment:
<<: *prod-env
restart: always