"""Запуск: python -m splitapi (настройки из переменных окружения, см. docs/DEPLOY.md).""" from __future__ import annotations import logging import os import signal import sys import threading import urllib.request from . import security from .config import ConfigError, from_env, is_local_host from .http_api import make_server from .mailer import AsyncMailer, make_mailer from .ratelimit import RateLimiter from .service import Service from .store import Store log = logging.getLogger("splitapi") def healthcheck() -> int: """Для HEALTHCHECK контейнера: в образе нет curl, поэтому проверяем сами.""" port = os.environ.get("PORT") or "8000" try: with urllib.request.urlopen(f"http://127.0.0.1:{port}/api/health", timeout=3) as response: return 0 if response.status == 200 else 1 except OSError: return 1 def main() -> int: logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(name)s: %(message)s") if "--healthcheck" in sys.argv: return healthcheck() try: cfg = from_env(os.environ) except ConfigError as exc: log.critical("Неверные настройки: %s", exc) return 2 if cfg.mail_backend == "console": log.warning("SMTP не настроен: письма (со ссылками подтверждения и сброса) будут печататься в этот лог. Только для разработки!") elif cfg.smtp_security == "none" and not is_local_host(cfg.smtp_host): log.warning("SMTP_SECURITY=none: письма и пароль SMTP уходят на %s без шифрования. Используйте starttls или ssl.", cfg.smtp_host) security.verify_dummy("прогрев") # первый вход с несуществующей почтой не должен выдавать себя лишней задержкой mailer = make_mailer(cfg) service = Service(cfg, Store(cfg.db_path), mailer, RateLimiter()) server = make_server(cfg, service) def stop(signum: int, _frame: object) -> None: log.info("получен сигнал %s, останавливаюсь", signum) threading.Thread(target=server.shutdown, daemon=True).start() signal.signal(signal.SIGTERM, stop) signal.signal(signal.SIGINT, stop) log.info("API слушает %s:%s, сайт: %s", cfg.bind, cfg.port, cfg.public_url) try: server.serve_forever() finally: server.server_close() if isinstance(mailer, AsyncMailer): mailer.flush(timeout=5) mailer.close() return 0 if __name__ == "__main__": sys.exit(main())