""" main.py ======= Container entry point. Watches the mounted Suwayomi download directory and, after a quiet period, triggers SuwayomiMover (which also runs the Kavita person sync for every processed series). Mount points (Docker) --------------------- /mnt/suwayomi -> Suwayomi downloads (read/write, sources deleted) /mnt/kavita -> Kavita library (read/write, CBZs written here) Environment variables --------------------- Required: KAVITA_URL base URL of the Kavita server, e.g. http://kavita:5000 KAVITA_API_KEY Kavita API key (Settings → User → API key) Optional: SUWAYOMI_PATH default /mnt/suwayomi KAVITA_PATH default /mnt/kavita LANGUAGE default en SETTLE_SECONDS default 600 (10-minute quiet window) REQUEST_TIMEOUT default 30 DELETE_SOURCE default true (delete source folders after pack) MATCH_PATH default /config/matches.json WEB_PORT default 8080 (Flask web UI for matches.json) WEB_HOST default 0.0.0.0 """ from __future__ import annotations import os import signal import sys from pathlib import Path # Make src/ importable when running as `python main.py`. sys.path.insert(0, str(Path(__file__).resolve().parent / "src")) from src.SuwayomiMover import SuwayomiMover # noqa: E402 from src.SuwayomiFolderWatcher import SuwayomiFolderWatcher # noqa: E402 from src.MatchesCache import MatchesCache # noqa: E402 from src.MatchesWebApp import MatchesWebApp # noqa: E402 def _env_str(name: str, default: "str | None" = None, required: bool = False) -> "str | None": value = os.environ.get(name, default) if required and not value: print(f"[main] missing required env var: {name}", flush=True) sys.exit(2) return value def _env_int(name: str, default: int) -> int: raw = os.environ.get(name) if raw is None or raw == "": return default try: return int(raw) except ValueError: print(f"[main] {name}={raw!r} is not a valid integer; " f"falling back to {default}", flush=True) return default def _env_bool(name: str, default: bool) -> bool: raw = os.environ.get(name) if raw is None: return default return raw.strip().lower() in ("1", "true", "yes", "y", "on") def main() -> int: suwayomi_path = _env_str("SUWAYOMI_PATH", r"/mnt/suwayomi") kavita_path = _env_str("KAVITA_PATH", "/mnt/kavita") kavita_url = _env_str("KAVITA_URL", "http://kavita:5000") kavita_api_key = _env_str("KAVITA_API_KEY", "") language = _env_str("LANGUAGE", "en") or "en" settle_seconds = _env_int("SETTLE_SECONDS", 600) request_timeout = _env_int("REQUEST_TIMEOUT", 30) delete_source = _env_bool("DELETE_SOURCE", True) match_path = _env_str("MATCH_PATH", "/config/matches.json") web_host = _env_str("WEB_HOST", "0.0.0.0") or "0.0.0.0" web_port = _env_int("WEB_PORT", 8080) print(f"[main] suwayomi = {suwayomi_path}", flush=True) print(f"[main] kavita = {kavita_path}", flush=True) print(f"[main] kavita url= {kavita_url}", flush=True) print(f"[main] settle = {settle_seconds}s", flush=True) print(f"[main] language = {language}", flush=True) print(f"[main] delete src= {delete_source}", flush=True) print(f"[main] match path= {match_path}", flush=True) print(f"[main] web = {web_host}:{web_port}", flush=True) matches_cache = MatchesCache(match_path) mover = SuwayomiMover( suwayomi_path, kavita_path, kavita_base_url=kavita_url, kavita_api_key=kavita_api_key, language=language, request_timeout=request_timeout, delete_source=delete_source, matches_cache=matches_cache, ) # watcher = SuwayomiFolderWatcher(suwayomi_path, mover, settle_seconds=settle_seconds) web_app = MatchesWebApp(matches_cache, mover=mover, host=web_host, port=web_port) web_app.start() # def shutdown(signum, _frame): # print(f"[main] received signal {signum}", flush=True) # watcher.stop() # # signal.signal(signal.SIGTERM, shutdown) # signal.signal(signal.SIGINT, shutdown) # # watcher.start() # watcher.wait() # blocks until stop() is called via a signal web_app.wait() # keep process alive while the watcher is disabled return 0 if __name__ == "__main__": sys.exit(main())