Решение по TD-STYLE-ANALYZERS: LF — инструменты проекта (Python/Node) пишут LF, CRLF-.sh не работают на Linux CI (sh scripts/ci.sh), большинство файлов уже были LF. Добавлен .gitattributes (* text=auto eol=lf, бинарные исключения), .editorconfig переведён на lf, 1029 файлов конвертированы, git add --renormalize. Из индекса убраны закравшиеся archive/**/__pycache__/*.pyc.
100 lines
4.6 KiB
Python
100 lines
4.6 KiB
Python
"""Конфигурация из переменных окружения.
|
||
|
||
По ТЗ в env выносятся ТОЛЬКО неконфиденциальные параметры:
|
||
порты, пути к БД/данным/сессиям. Секреты (Telegram api_id/hash,
|
||
ключи AI, пароль дашборда) живут в базе и задаются через UI.
|
||
"""
|
||
from __future__ import annotations
|
||
|
||
import os
|
||
from pathlib import Path
|
||
|
||
|
||
def _bool(name: str, default: bool = False) -> bool:
|
||
raw = os.getenv(name)
|
||
if raw is None:
|
||
return default
|
||
return raw.strip().lower() in {"1", "true", "yes", "on"}
|
||
|
||
|
||
def _int(name: str, default: int) -> int:
|
||
try:
|
||
return int(os.getenv(name, str(default)))
|
||
except ValueError:
|
||
return default
|
||
|
||
|
||
# Корень данных (том в docker-compose). Внутри лежат leadradar.duckdb,
|
||
# telegram-сессии, файлы вложений (позже — MinIO), секрет подписи сессий.
|
||
DATA_DIR = Path(os.getenv("LEADRADAR_DATA", str(Path(__file__).resolve().parent.parent / "data"))).resolve()
|
||
DB_PATH = DATA_DIR / os.getenv("LEADRADAR_DB_NAME", "leadradar.duckdb")
|
||
SESSIONS_DIR = DATA_DIR / "telegram_sessions"
|
||
FILES_DIR = DATA_DIR / "attachments"
|
||
SECRET_FILE = DATA_DIR / "session_secret.key"
|
||
|
||
# Хост/порт HTTP-сервера
|
||
HOST = os.getenv("LEADRADAR_HOST", "0.0.0.0")
|
||
PORT = _int("LEADRADAR_PORT", 8000)
|
||
|
||
# Через env допустимо задать стартовый секрет (например, из секрет-менеджера),
|
||
# но если он не задан — будет создан случайный и сохранён в SECRET_FILE.
|
||
SESSION_SECRET = os.getenv("LEADRADAR_SESSION_SECRET", "") or None
|
||
|
||
# Учётные данные первого входа можно выставить и в env (удобно для развёртывания),
|
||
# НО по умолчанию система стартует с admin/admin и требует смены пароля.
|
||
BOOTSTRAP_LOGIN = os.getenv("LEADRADAR_BOOTSTRAP_LOGIN", "admin")
|
||
BOOTSTRAP_PASSWORD = os.getenv("LEADRADAR_BOOTSTRAP_PASSWORD", "admin")
|
||
|
||
# Шифрование секретов в БД. По договорённости ключ живёт в env;
|
||
# при локальной разработке без env создаётся файл data/encryption.key.
|
||
ENCRYPTION_KEY = os.getenv("LEADRADAR_ENCRYPTION_KEY", "") or None
|
||
ENCRYPTION_KEY_FILE = DATA_DIR / "encryption.key"
|
||
|
||
# MinIO (вложения; креды по договорённости — в env)
|
||
MINIO_ENDPOINT = os.getenv("LEADRADAR_MINIO_ENDPOINT", "")
|
||
MINIO_ACCESS_KEY = os.getenv("LEADRADAR_MINIO_ACCESS_KEY", "")
|
||
MINIO_SECRET_KEY = os.getenv("LEADRADAR_MINIO_SECRET_KEY", "")
|
||
MINIO_BUCKET = os.getenv("LEADRADAR_MINIO_BUCKET", "leadradar")
|
||
MINIO_SECURE = _bool("LEADRADAR_MINIO_SECURE", False)
|
||
|
||
# Автономный ML-сервис (отдельный контейнер). Если недоступен — модель не
|
||
# используется в пайплайне, а события обучения копятся в outbox до его возврата.
|
||
ML_URL = os.getenv("LEADRADAR_ML_URL", "http://127.0.0.1:8100").rstrip("/")
|
||
ML_TIMEOUT = _int("LEADRADAR_ML_TIMEOUT", 30) # обучение батчами — тяжёлое, таймаут щедрый
|
||
|
||
# Куки
|
||
COOKIE_NAME = "leadradar_session"
|
||
SESSION_DAYS = 30
|
||
|
||
# Telegram-сессия (Telethon)
|
||
SESSION_PREFIX = "user"
|
||
|
||
# Путь к статике фронтенда (собранный dist). Если папки нет — API работает
|
||
# отдельно (фронт поднимается dev-сервером), статика просто не раздаётся.
|
||
FRONTEND_DIST = Path(os.getenv("LEADRADAR_FRONTEND_DIST", str(Path(__file__).resolve().parent.parent.parent / "frontend" / "dist"))).resolve()
|
||
|
||
# Логи
|
||
LOG_LEVEL = os.getenv("LEADRADAR_LOG_LEVEL", "INFO")
|
||
|
||
# Демо-эндпоинты (simulate-lead, age-lead). В проде выключено;
|
||
# включается только для локальных тестов разработчика: LEADRADAR_DEMO=1
|
||
DEMO_ENABLED = _bool("LEADRADAR_DEMO", False)
|
||
|
||
|
||
def ensure_dirs() -> None:
|
||
DATA_DIR.mkdir(parents=True, exist_ok=True)
|
||
SESSIONS_DIR.mkdir(parents=True, exist_ok=True)
|
||
FILES_DIR.mkdir(parents=True, exist_ok=True)
|
||
|
||
|
||
def get_or_create_secret() -> str:
|
||
if SESSION_SECRET:
|
||
return SESSION_SECRET
|
||
if SECRET_FILE.exists():
|
||
return SECRET_FILE.read_text(encoding="utf-8").strip()
|
||
import secrets
|
||
|
||
secret = secrets.token_hex(32)
|
||
SECRET_FILE.write_text(secret, encoding="utf-8")
|
||
return secret
|