From de31826137bb6545a5e1730958aba4600ce98a9c Mon Sep 17 00:00:00 2001 From: SergeantPanda Date: Thu, 18 Dec 2025 16:54:59 -0600 Subject: [PATCH] refactor: externalize Redis and Celery configuration via environment variables Replace hardcoded localhost:6379 values throughout codebase with environment-based configuration. Add REDIS_PORT support and allow REDIS_URL override for external Redis services. Configure Celery broker/result backend to use Redis settings with environment variable overrides. Closes #762 --- CHANGELOG.md | 2 ++ apps/proxy/ts_proxy/client_manager.py | 6 ++++-- apps/proxy/vod_proxy/connection_manager.py | 6 +++++- apps/proxy/vod_proxy/views.py | 6 +++++- core/views.py | 4 +++- dispatcharr/persistent_lock.py | 8 ++++++-- dispatcharr/settings.py | 11 +++++++---- 7 files changed, 32 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 15a176a8..370efa71 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Updated dependencies: Django (5.2.4 → 5.2.9) includes CVE security patch, psycopg2-binary (2.9.10 → 2.9.11), celery (5.5.3 → 5.6.0), djangorestframework (3.16.0 → 3.16.1), requests (2.32.4 → 2.32.5), psutil (7.0.0 → 7.1.3), gevent (25.5.1 → 25.9.1), rapidfuzz (3.13.0 → 3.14.3), torch (2.7.1 → 2.9.1), sentence-transformers (5.1.0 → 5.2.0), lxml (6.0.0 → 6.0.2) (Closes #662) - Frontend dependencies updated: Vite (6.2.0 → 7.1.7), ESLint (9.21.0 → 9.27.0), and related packages; added npm `overrides` to enforce js-yaml@^4.1.1 for transitive security fix. All 6 reported vulnerabilities resolved with `npm audit fix`. - Floating video player now supports resizing via a drag handles, with minimum size enforcement and viewport/page boundary constraints to keep it visible. +- Redis connection settings now fully configurable via environment variables (`REDIS_HOST`, `REDIS_PORT`, `REDIS_DB`, `REDIS_URL`), replacing hardcoded `localhost:6379` values throughout the codebase. This enables use of external Redis services in production deployments. (Closes #762) +- Celery broker and result backend URLs now respect `REDIS_HOST`/`REDIS_PORT`/`REDIS_DB` settings as defaults, with `CELERY_BROKER_URL` and `CELERY_RESULT_BACKEND` environment variables available for override. ### Fixed diff --git a/apps/proxy/ts_proxy/client_manager.py b/apps/proxy/ts_proxy/client_manager.py index bffecdde..a361bfa1 100644 --- a/apps/proxy/ts_proxy/client_manager.py +++ b/apps/proxy/ts_proxy/client_manager.py @@ -48,9 +48,11 @@ class ClientManager: # Import here to avoid potential import issues from apps.proxy.ts_proxy.channel_status import ChannelStatus import redis + from django.conf import settings - # Get all channels from Redis - redis_client = redis.Redis.from_url('redis://localhost:6379', decode_responses=True) + # Get all channels from Redis using settings + redis_url = getattr(settings, 'REDIS_URL', 'redis://localhost:6379/0') + redis_client = redis.Redis.from_url(redis_url, decode_responses=True) all_channels = [] cursor = 0 diff --git a/apps/proxy/vod_proxy/connection_manager.py b/apps/proxy/vod_proxy/connection_manager.py index dea5759b..ec0bffa5 100644 --- a/apps/proxy/vod_proxy/connection_manager.py +++ b/apps/proxy/vod_proxy/connection_manager.py @@ -97,7 +97,11 @@ class PersistentVODConnection: # First check if we have a pre-stored content length from HEAD request try: import redis - r = redis.StrictRedis(host='localhost', port=6379, db=0, decode_responses=True) + from django.conf import settings + redis_host = getattr(settings, 'REDIS_HOST', 'localhost') + redis_port = int(getattr(settings, 'REDIS_PORT', 6379)) + redis_db = int(getattr(settings, 'REDIS_DB', 0)) + r = redis.StrictRedis(host=redis_host, port=redis_port, db=redis_db, decode_responses=True) content_length_key = f"vod_content_length:{self.session_id}" stored_length = r.get(content_length_key) if stored_length: diff --git a/apps/proxy/vod_proxy/views.py b/apps/proxy/vod_proxy/views.py index f3aca3fc..2ec95cc3 100644 --- a/apps/proxy/vod_proxy/views.py +++ b/apps/proxy/vod_proxy/views.py @@ -329,7 +329,11 @@ class VODStreamView(View): # Store the total content length in Redis for the persistent connection to use try: import redis - r = redis.StrictRedis(host='localhost', port=6379, db=0, decode_responses=True) + from django.conf import settings + redis_host = getattr(settings, 'REDIS_HOST', 'localhost') + redis_port = int(getattr(settings, 'REDIS_PORT', 6379)) + redis_db = int(getattr(settings, 'REDIS_DB', 0)) + r = redis.StrictRedis(host=redis_host, port=redis_port, db=redis_db, decode_responses=True) content_length_key = f"vod_content_length:{session_id}" r.set(content_length_key, total_size, ex=1800) # Store for 30 minutes logger.info(f"[VOD-HEAD] Stored total content length {total_size} for session {session_id}") diff --git a/core/views.py b/core/views.py index d10df027..312d8836 100644 --- a/core/views.py +++ b/core/views.py @@ -37,7 +37,9 @@ def stream_view(request, channel_uuid): """ try: redis_host = getattr(settings, "REDIS_HOST", "localhost") - redis_client = redis.Redis(host=settings.REDIS_HOST, port=6379, db=int(getattr(settings, "REDIS_DB", "0"))) + redis_port = int(getattr(settings, "REDIS_PORT", 6379)) + redis_db = int(getattr(settings, "REDIS_DB", "0")) + redis_client = redis.Redis(host=redis_host, port=redis_port, db=redis_db) # Retrieve the channel by the provided stream_id. channel = Channel.objects.get(uuid=channel_uuid) diff --git a/dispatcharr/persistent_lock.py b/dispatcharr/persistent_lock.py index 360c9b5d..27d480be 100644 --- a/dispatcharr/persistent_lock.py +++ b/dispatcharr/persistent_lock.py @@ -73,8 +73,12 @@ class PersistentLock: # Example usage (for testing purposes only): if __name__ == "__main__": - # Connect to Redis on localhost; adjust connection parameters as needed. - client = redis.Redis(host="localhost", port=6379, db=0) + import os + # Connect to Redis using environment variables; adjust connection parameters as needed. + redis_host = os.environ.get("REDIS_HOST", "localhost") + redis_port = int(os.environ.get("REDIS_PORT", 6379)) + redis_db = int(os.environ.get("REDIS_DB", 0)) + client = redis.Redis(host=redis_host, port=redis_port, db=redis_db) lock = PersistentLock(client, "lock:example_account", lock_timeout=120) if lock.acquire(): diff --git a/dispatcharr/settings.py b/dispatcharr/settings.py index 556fb39d..1a9a1a44 100644 --- a/dispatcharr/settings.py +++ b/dispatcharr/settings.py @@ -6,6 +6,7 @@ BASE_DIR = Path(__file__).resolve().parent.parent SECRET_KEY = os.environ.get("DJANGO_SECRET_KEY") REDIS_HOST = os.environ.get("REDIS_HOST", "localhost") +REDIS_PORT = int(os.environ.get("REDIS_PORT", 6379)) REDIS_DB = os.environ.get("REDIS_DB", "0") # Set DEBUG to True for development, False for production @@ -118,7 +119,7 @@ CHANNEL_LAYERS = { "default": { "BACKEND": "channels_redis.core.RedisChannelLayer", "CONFIG": { - "hosts": [(REDIS_HOST, 6379, REDIS_DB)], # Ensure Redis is running + "hosts": [(REDIS_HOST, REDIS_PORT, REDIS_DB)], # Ensure Redis is running }, }, } @@ -184,8 +185,10 @@ STATICFILES_DIRS = [ DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField" AUTH_USER_MODEL = "accounts.User" -CELERY_BROKER_URL = os.environ.get("CELERY_BROKER_URL", "redis://localhost:6379/0") -CELERY_RESULT_BACKEND = CELERY_BROKER_URL +# Build default Redis URL from components for Celery +_default_redis_url = f"redis://{REDIS_HOST}:{REDIS_PORT}/{REDIS_DB}" +CELERY_BROKER_URL = os.environ.get("CELERY_BROKER_URL", _default_redis_url) +CELERY_RESULT_BACKEND = os.environ.get("CELERY_RESULT_BACKEND", CELERY_BROKER_URL) # Configure Redis key prefix CELERY_RESULT_BACKEND_TRANSPORT_OPTIONS = { @@ -249,7 +252,7 @@ SIMPLE_JWT = { } # Redis connection settings -REDIS_URL = "redis://localhost:6379/0" +REDIS_URL = os.environ.get("REDIS_URL", f"redis://{REDIS_HOST}:{REDIS_PORT}/{REDIS_DB}") REDIS_SOCKET_TIMEOUT = 60 # Socket timeout in seconds REDIS_SOCKET_CONNECT_TIMEOUT = 5 # Connection timeout in seconds REDIS_HEALTH_CHECK_INTERVAL = 15 # Health check every 15 seconds