"""Entry point: wire up the store, the background engine and the web server. A single process hosts both the Flask web UI/API and the RSS->ntfy engine (running in its own daemon thread). Served by waitress so it works the same on Linux containers and Windows. """ import logging import os import signal import sys from waitress import serve import store as store_mod from engine import Engine from webapp import create_app def setup_logging(engine: Engine): logger = logging.getLogger("bridge") logger.setLevel(logging.INFO) fmt = logging.Formatter("%(asctime)s [%(levelname)s] %(message)s", "%Y-%m-%d %H:%M:%S") stream = logging.StreamHandler(sys.stdout) stream.setFormatter(fmt) logger.addHandler(stream) file_handler = logging.FileHandler(store_mod.LOG_PATH, encoding="utf-8") file_handler.setFormatter(fmt) logger.addHandler(file_handler) logger.addHandler(engine.ring) # in-memory buffer for the web UI logger.propagate = False def main(): host = os.environ.get("HOST", "0.0.0.0") port = int(os.environ.get("PORT", "8080")) store = store_mod.Store() engine = Engine(store) setup_logging(engine) engine.start() app = create_app(store, engine) def shutdown(signum, frame): # noqa: ARG001 logging.getLogger("bridge").info("Shutting down (signal %s)", signum) engine.stop() sys.exit(0) signal.signal(signal.SIGINT, shutdown) signal.signal(signal.SIGTERM, shutdown) logging.getLogger("bridge").info("Web UI on http://%s:%s", host, port) serve(app, host=host, port=port, threads=8) if __name__ == "__main__": main()