diff --git a/.env-sample b/.env-sample index 0ae5cde..a513a5f 100644 --- a/.env-sample +++ b/.env-sample @@ -3,6 +3,7 @@ ADDON_NAME=Comet # for Stremio FASTAPI_HOST=0.0.0.0 FASTAPI_PORT=8000 FASTAPI_WORKERS=1 +USE_GUNICORN=True # will use uvicorn if False or if on Windows DASHBOARD_ADMIN_PASSWORD=CHANGE_ME # The password to access the dashboard with active connections and soon more... DATABASE_TYPE=sqlite # or postgresql if you're making a Comet cluster DATABASE_URL=username:password@hostname:port # to connect to PostgreSQL diff --git a/comet/main.py b/comet/main.py index 41ac902..0868473 100644 --- a/comet/main.py +++ b/comet/main.py @@ -5,6 +5,7 @@ import time import traceback import uvicorn +import os from contextlib import asynccontextmanager @@ -101,17 +102,6 @@ def signal_handler(sig, frame): signal.signal(signal.SIGINT, signal_handler) signal.signal(signal.SIGTERM, signal_handler) -config = uvicorn.Config( - app, - host=settings.FASTAPI_HOST, - port=settings.FASTAPI_PORT, - proxy_headers=True, - forwarded_allow_ips="*", - workers=settings.FASTAPI_WORKERS, - log_config=None, -) -server = Server(config=config) - def start_log(): logger.log( @@ -170,15 +160,77 @@ def start_log(): logger.log("COMET", f"Custom Header HTML: {bool(settings.CUSTOM_HEADER_HTML)}") -with server.run_in_thread(): +def run_with_uvicorn(): + """Run the server with uvicorn only""" + config = uvicorn.Config( + app, + host=settings.FASTAPI_HOST, + port=settings.FASTAPI_PORT, + proxy_headers=True, + forwarded_allow_ips="*", + workers=settings.FASTAPI_WORKERS, + log_config=None, + ) + server = Server(config=config) + + with server.run_in_thread(): + start_log() + try: + while True: + time.sleep(1) # Keep the main thread alive + except KeyboardInterrupt: + logger.log("COMET", "Server stopped by user") + except Exception as e: + logger.error(f"Unexpected error: {e}") + logger.exception(traceback.format_exc()) + finally: + logger.log("COMET", "Server Shutdown") + + +def run_with_gunicorn(): + """Run the server with gunicorn and uvicorn workers""" + import gunicorn.app.base + + class StandaloneApplication(gunicorn.app.base.BaseApplication): + def __init__(self, app, options=None): + self.options = options or {} + self.application = app + super().__init__() + + def load_config(self): + config = { + key: value for key, value in self.options.items() + if key in self.cfg.settings and value is not None + } + for key, value in config.items(): + self.cfg.set(key.lower(), value) + + def load(self): + return self.application + + workers = settings.FASTAPI_WORKERS + if workers <= 1: + workers = (os.cpu_count() or 1) * 2 + 1 + + options = { + "bind": f"{settings.FASTAPI_HOST}:{settings.FASTAPI_PORT}", + "workers": workers, + "worker_class": "uvicorn.workers.UvicornWorker", + "timeout": 120, + "keepalive": 5, + "preload_app": True, + "proxy_protocol": True, + "forwarded_allow_ips": "*", + } + start_log() - try: - while True: - time.sleep(1) # Keep the main thread alive - except KeyboardInterrupt: - logger.log("COMET", "Server stopped by user") - except Exception as e: - logger.error(f"Unexpected error: {e}") - logger.exception(traceback.format_exc()) - finally: - logger.log("COMET", "Server Shutdown") + logger.log("COMET", f"Starting with gunicorn using {workers} workers") + + StandaloneApplication(app, options).run() + + +if __name__ == "__main__": + if os.name == "nt" or not settings.USE_GUNICORN: + run_with_uvicorn() + else: + run_with_gunicorn() \ No newline at end of file diff --git a/comet/utils/models.py b/comet/utils/models.py index 26a9b63..6690a91 100644 --- a/comet/utils/models.py +++ b/comet/utils/models.py @@ -31,6 +31,7 @@ class AppSettings(BaseSettings): FASTAPI_HOST: Optional[str] = "0.0.0.0" FASTAPI_PORT: Optional[int] = 8000 FASTAPI_WORKERS: Optional[int] = 1 # 2 * (os.cpu_count() or 1) + USE_GUNICORN: Optional[bool] = True DASHBOARD_ADMIN_PASSWORD: Optional[str] = "".join( random.choices(string.ascii_letters + string.digits, k=16) ) diff --git a/docker-compose.yaml b/docker-compose.yaml index cee5710..a9a1b28 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -1,14 +1,17 @@ -volumes: - comet_data: - services: comet: container_name: comet - image: g0ldyy/comet + image: g0ldyy/comet:rewrite restart: unless-stopped ports: - "8000:8000" env_file: - .env volumes: - - comet_data:/data + - ./data:/data + healthcheck: + test: wget -qO- http://127.0.0.1:8000/health + interval: 30s + timeout: 10s + retries: 3 + start_period: 20s diff --git a/pyproject.toml b/pyproject.toml index 36758d1..4ff513f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -16,6 +16,7 @@ dependencies = [ "databases", "demagnetize", "fastapi", + "gunicorn", "jinja2", "loguru", "mediaflow-proxy",