Merge branch 'dev' of https://github.com/Dispatcharr/Dispatcharr into dev
Some checks failed
CI Pipeline / prepare (push) Waiting to run
CI Pipeline / docker (amd64, ubuntu-24.04) (push) Blocked by required conditions
CI Pipeline / docker (arm64, ubuntu-24.04-arm) (push) Blocked by required conditions
CI Pipeline / create-manifest (push) Blocked by required conditions
Build and Push Multi-Arch Docker Image / build-and-push (push) Waiting to run
Frontend Tests / test (push) Has been cancelled

This commit is contained in:
SergeantPanda 2026-02-09 17:35:09 -06:00
commit 0db19d7314
10 changed files with 176 additions and 24 deletions

View file

@ -149,11 +149,15 @@ class ProxyServer:
redis_host = os.environ.get("REDIS_HOST", getattr(settings, 'REDIS_HOST', 'localhost'))
redis_port = int(os.environ.get("REDIS_PORT", getattr(settings, 'REDIS_PORT', 6379)))
redis_db = int(os.environ.get("REDIS_DB", getattr(settings, 'REDIS_DB', 0)))
redis_password = os.environ.get("REDIS_PASSWORD", getattr(settings, 'REDIS_PASSWORD', ''))
redis_user = os.environ.get("REDIS_USER", getattr(settings, 'REDIS_USER', ''))
pubsub_client = redis.Redis(
host=redis_host,
port=redis_port,
db=redis_db,
password=redis_password if redis_password else None,
username=redis_user if redis_user else None,
socket_timeout=60,
socket_connect_timeout=10,
socket_keepalive=True,

View file

@ -101,7 +101,16 @@ class PersistentVODConnection:
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)
redis_password = getattr(settings, 'REDIS_PASSWORD', '')
redis_user = getattr(settings, 'REDIS_USER', '')
r = redis.StrictRedis(
host=redis_host,
port=redis_port,
db=redis_db,
password=redis_password if redis_password else None,
username=redis_user if redis_user else None,
decode_responses=True
)
content_length_key = f"vod_content_length:{self.session_id}"
stored_length = r.get(content_length_key)
if stored_length:

View file

@ -333,7 +333,16 @@ class VODStreamView(View):
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)
redis_password = getattr(settings, 'REDIS_PASSWORD', '')
redis_user = getattr(settings, 'REDIS_USER', '')
r = redis.StrictRedis(
host=redis_host,
port=redis_port,
db=redis_db,
password=redis_password if redis_password else None,
username=redis_user if redis_user else None,
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}")

View file

@ -55,6 +55,8 @@ class RedisClient:
redis_host = os.environ.get("REDIS_HOST", getattr(settings, 'REDIS_HOST', 'localhost'))
redis_port = int(os.environ.get("REDIS_PORT", getattr(settings, 'REDIS_PORT', 6379)))
redis_db = int(os.environ.get("REDIS_DB", getattr(settings, 'REDIS_DB', 0)))
redis_password = os.environ.get("REDIS_PASSWORD", getattr(settings, 'REDIS_PASSWORD', ''))
redis_user = os.environ.get("REDIS_USER", getattr(settings, 'REDIS_USER', ''))
# Use standardized settings
socket_timeout = getattr(settings, 'REDIS_SOCKET_TIMEOUT', 5)
@ -68,6 +70,8 @@ class RedisClient:
host=redis_host,
port=redis_port,
db=redis_db,
password=redis_password if redis_password else None,
username=redis_user if redis_user else None,
socket_timeout=socket_timeout,
socket_connect_timeout=socket_connect_timeout,
socket_keepalive=socket_keepalive,
@ -148,6 +152,8 @@ class RedisClient:
redis_host = os.environ.get("REDIS_HOST", getattr(settings, 'REDIS_HOST', 'localhost'))
redis_port = int(os.environ.get("REDIS_PORT", getattr(settings, 'REDIS_PORT', 6379)))
redis_db = int(os.environ.get("REDIS_DB", getattr(settings, 'REDIS_DB', 0)))
redis_password = os.environ.get("REDIS_PASSWORD", getattr(settings, 'REDIS_PASSWORD', ''))
redis_user = os.environ.get("REDIS_USER", getattr(settings, 'REDIS_USER', ''))
# Use standardized settings but without socket timeouts for PubSub
# Important: socket_timeout is None for PubSub operations
@ -161,6 +167,8 @@ class RedisClient:
host=redis_host,
port=redis_port,
db=redis_db,
password=redis_password if redis_password else None,
username=redis_user if redis_user else None,
socket_timeout=None, # Critical: No timeout for PubSub operations
socket_connect_timeout=socket_connect_timeout,
socket_keepalive=socket_keepalive,

View file

@ -40,7 +40,15 @@ def stream_view(request, channel_uuid):
redis_host = getattr(settings, "REDIS_HOST", "localhost")
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)
redis_password = getattr(settings, "REDIS_PASSWORD", "")
redis_user = getattr(settings, "REDIS_USER", "")
redis_client = redis.Redis(
host=redis_host,
port=redis_port,
db=redis_db,
password=redis_password if redis_password else None,
username=redis_user if redis_user else None
)
# Retrieve the channel by the provided stream_id.
channel = Channel.objects.get(uuid=channel_uuid)

View file

@ -78,7 +78,15 @@ if __name__ == "__main__":
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)
redis_password = os.environ.get("REDIS_PASSWORD", "")
redis_user = os.environ.get("REDIS_USER", "")
client = redis.Redis(
host=redis_host,
port=redis_port,
db=redis_db,
password=redis_password if redis_password else None,
username=redis_user if redis_user else None
)
lock = PersistentLock(client, "lock:example_account", lock_timeout=120)
if lock.acquire():

View file

@ -1,6 +1,7 @@
import os
from pathlib import Path
from datetime import timedelta
from urllib.parse import quote_plus
BASE_DIR = Path(__file__).resolve().parent.parent
@ -8,6 +9,8 @@ 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")
REDIS_USER = os.environ.get("REDIS_USER", "")
REDIS_PASSWORD = os.environ.get("REDIS_PASSWORD", "")
# Set DEBUG to True for development, False for production
if os.environ.get("DISPATCHARR_DEBUG", "False").lower() == "true":
@ -120,7 +123,12 @@ CHANNEL_LAYERS = {
"default": {
"BACKEND": "channels_redis.core.RedisChannelLayer",
"CONFIG": {
"hosts": [(REDIS_HOST, REDIS_PORT, REDIS_DB)], # Ensure Redis is running
"hosts": ["redis://{redis_auth}{host}:{port}/{db}".format(
redis_auth=f"{quote_plus(REDIS_USER)}:{quote_plus(REDIS_PASSWORD)}@" if REDIS_PASSWORD and REDIS_USER else f":{quote_plus(REDIS_PASSWORD)}@" if REDIS_PASSWORD else "",
host=REDIS_HOST,
port=REDIS_PORT,
db=REDIS_DB
)], # URL format supports authentication
},
},
}
@ -198,8 +206,19 @@ STATICFILES_DIRS = [
DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
AUTH_USER_MODEL = "accounts.User"
# Build default Redis URL from components for Celery
_default_redis_url = f"redis://{REDIS_HOST}:{REDIS_PORT}/{REDIS_DB}"
# Build default Redis URL from components for Celery with optional authentication
# Build auth string conditionally with URL encoding for special characters
if REDIS_PASSWORD:
encoded_password = quote_plus(REDIS_PASSWORD)
if REDIS_USER:
encoded_user = quote_plus(REDIS_USER)
redis_auth = f"{encoded_user}:{encoded_password}@"
else:
redis_auth = f":{encoded_password}@"
else:
redis_auth = ""
_default_redis_url = f"redis://{redis_auth}{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)
@ -270,7 +289,7 @@ SIMPLE_JWT = {
}
# Redis connection settings
REDIS_URL = os.environ.get("REDIS_URL", f"redis://{REDIS_HOST}:{REDIS_PORT}/{REDIS_DB}")
REDIS_URL = os.environ.get("REDIS_URL", _default_redis_url)
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

View file

@ -1,4 +1,11 @@
# Dispatcharr - Modular Deployment Configuration
# This compose file runs Dispatcharr in modular mode with separate containers
# for web, celery workers, PostgreSQL database, and Redis cache.
services:
# ============================================================================
# Web Service
# ============================================================================
web:
image: ghcr.io/dispatcharr/dispatcharr:latest
container_name: dispatcharr_web
@ -9,44 +16,67 @@ services:
depends_on:
- db
- redis
# --- Environment Configuration ---
environment:
# Deployment Mode
- DISPATCHARR_ENV=modular
# PostgreSQL Connection
- POSTGRES_HOST=db
- POSTGRES_PORT=5432
- POSTGRES_DB=dispatcharr
- POSTGRES_USER=dispatch
- POSTGRES_PASSWORD=secret
# Redis Connection
- REDIS_HOST=redis
- REDIS_PORT=6379
# Redis Authentication (Optional)
# Uncomment and set if your Redis requires authentication:
#- REDIS_PASSWORD=your_strong_redis_password
#- REDIS_USER=your_redis_username # For Redis 6+ ACL - see Redis service below
# Logging
- DISPATCHARR_LOG_LEVEL=info
# Legacy CPU Support (Optional)
# Uncomment to enable legacy NumPy build for older CPUs (circa 2009)
# that lack support for newer baseline CPU features
# that lack support for newer baseline CPU features:
#- USE_LEGACY_NUMPY=true
# Process Priority Configuration (Optional)
# Lower values = higher priority. Range: -20 (highest) to 19 (lowest)
# Negative values require cap_add: SYS_NICE (uncomment below)
# Negative values require cap_add: SYS_NICE (see below)
#- UWSGI_NICE_LEVEL=-5 # uWSGI/FFmpeg/Streaming (default: 0, recommended: -5 for high priority)
#
# --- Advanced Configuration ---
# Uncomment to enable high priority for streaming (required if UWSGI_NICE_LEVEL < 0)
#cap_add:
# - SYS_NICE
# Optional for hardware acceleration
# --- Hardware Acceleration (Optional) ---
# Uncomment for GPU access (transcoding acceleration)
#group_add:
# - video
# #- render # Uncomment if your GPU requires it
# #- render # Uncomment if your GPU requires it
#devices:
# - /dev/dri:/dev/dri # For Intel/AMD GPU acceleration (VA-API)
# NVIDIA GPU Support (requires NVIDIA Container Toolkit)
# Uncomment the following lines for NVIDIA GPU support
# NVidia GPU support (requires NVIDIA Container Toolkit)
#deploy:
# resources:
# reservations:
# devices:
# - driver: nvidia
# count: all
# capabilities: [gpu]
# reservations:
# devices:
# - driver: nvidia
# count: all
# capabilities: [gpu]
# ============================================================================
# Celery Service - Background task worker
# ============================================================================
celery:
image: ghcr.io/dispatcharr/dispatcharr:latest
container_name: dispatcharr_celery
@ -58,24 +88,47 @@ services:
- ./data:/data
extra_hosts:
- "host.docker.internal:host-gateway"
entrypoint: ["/app/docker/entrypoint.celery.sh"]
# --- Environment Configuration ---
environment:
# Deployment Mode
- DISPATCHARR_ENV=modular
# PostgreSQL Connection
- POSTGRES_HOST=db
- POSTGRES_PORT=5432
- POSTGRES_DB=dispatcharr
- POSTGRES_USER=dispatch
- POSTGRES_PASSWORD=secret
# Redis Connection
- REDIS_HOST=redis
- REDIS_PORT=6379
# Redis Authentication (Optional)
# Uncomment and set if your Redis requires authentication:
#- REDIS_PASSWORD=your_strong_redis_password
#- REDIS_USER=your_redis_username # For Redis 6+ ACL - see Redis service below
# Logging
- DISPATCHARR_LOG_LEVEL=info
#- CELERY_NICE_LEVEL=5 #Celery/EPG/Background tasks (default:5, low priority; Range: -20 to 19)
# Process Priority Configuration (Optional)
#- CELERY_NICE_LEVEL=5 # Celery/EPG/Background tasks (default: 5, low priority; Range: -20 to 19)
# Django Configuration
- DJANGO_SETTINGS_MODULE=dispatcharr.settings
- PYTHONUNBUFFERED=1
# --- Advanced Configuration ---
# Uncomment to enable high priority for Celery (required if CELERY_NICE_LEVEL < 0)
#cap_add:
# - SYS_NICE
entrypoint: ["/app/docker/entrypoint.celery.sh"]
# ============================================================================
# PostgreSQL
# ============================================================================
db:
image: postgres:17
container_name: dispatcharr_db
@ -93,14 +146,42 @@ services:
timeout: 5s
retries: 5
# ============================================================================
# Redis
# ============================================================================
redis:
image: redis:latest
container_name: dispatcharr_redis
# --- Authentication Configuration (Optional) ---
# By default, Redis runs without authentication.
# Choose ONE of the following options if authentication is required:
# Option 1: Password-only authentication (Redis <6 or default user)
#command: ["redis-server", "--requirepass", "your_strong_redis_password"]
# Option 2: Redis 6+ ACL with username + password (requires custom config file - see Redis documentation for configuration)
#command: ["redis-server", "/etc/redis/redis.conf"]
#volumes:
# - ./redis.conf:/etc/redis/redis.conf:ro
# --- Health Check Configuration ---
healthcheck:
# Default: No authentication
test: ["CMD", "redis-cli", "ping"]
# If using Option 1 (password-only), uncomment this instead:
#test: ["CMD", "redis-cli", "-a", "your_strong_redis_password", "ping"]
# If using Option 2 (Redis 6+ ACL), uncomment this instead:
#test: ["CMD", "redis-cli", "--user", "your_redis_username", "-a", "your_strong_redis_password", "ping"]
interval: 5s
timeout: 5s
retries: 5
# ==============================================================================
# Volumes
# ==============================================================================
volumes:
postgres_data:

View file

@ -38,6 +38,8 @@ export PG_BINDIR="/usr/lib/postgresql/${PG_VERSION}/bin"
export REDIS_HOST=${REDIS_HOST:-localhost}
export REDIS_PORT=${REDIS_PORT:-6379}
export REDIS_DB=${REDIS_DB:-0}
export REDIS_PASSWORD=${REDIS_PASSWORD:-}
export REDIS_USER=${REDIS_USER:-}
export DISPATCHARR_PORT=${DISPATCHARR_PORT:-9191}
export LIBVA_DRIVERS_PATH='/usr/local/lib/x86_64-linux-gnu/dri'
export LD_LIBRARY_PATH='/usr/local/lib'
@ -104,7 +106,7 @@ if [[ ! -f /etc/profile.d/dispatcharr.sh ]]; then
PATH VIRTUAL_ENV DJANGO_SETTINGS_MODULE PYTHONUNBUFFERED PYTHONDONTWRITEBYTECODE
POSTGRES_DB POSTGRES_USER POSTGRES_PASSWORD POSTGRES_HOST POSTGRES_PORT
DISPATCHARR_ENV DISPATCHARR_DEBUG DISPATCHARR_LOG_LEVEL
REDIS_HOST REDIS_PORT REDIS_DB POSTGRES_DIR DISPATCHARR_PORT
REDIS_HOST REDIS_PORT REDIS_DB REDIS_PASSWORD REDIS_USER POSTGRES_DIR DISPATCHARR_PORT
DISPATCHARR_VERSION DISPATCHARR_TIMESTAMP LIBVA_DRIVERS_PATH LIBVA_DRIVER_NAME LD_LIBRARY_PATH
CELERY_NICE_LEVEL UWSGI_NICE_LEVEL DJANGO_SECRET_KEY
)

View file

@ -12,7 +12,7 @@ import logging
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)
def wait_for_redis(host='localhost', port=6379, db=0, max_retries=30, retry_interval=2):
def wait_for_redis(host='localhost', port=6379, db=0, password='', username='', max_retries=30, retry_interval=2):
"""Wait for Redis to become available"""
redis_client = None
retry_count = 0
@ -25,6 +25,8 @@ def wait_for_redis(host='localhost', port=6379, db=0, max_retries=30, retry_inte
host=host,
port=port,
db=db,
password=password if password else None,
username=username if username else None,
socket_timeout=2,
socket_connect_timeout=2
)
@ -50,12 +52,14 @@ if __name__ == "__main__":
host = os.environ.get('REDIS_HOST', 'localhost')
port = int(os.environ.get('REDIS_PORT', 6379))
db = int(os.environ.get('REDIS_DB', 0))
password = os.environ.get('REDIS_PASSWORD', '')
username = os.environ.get('REDIS_USER', '')
max_retries = int(os.environ.get('REDIS_WAIT_RETRIES', 30))
retry_interval = int(os.environ.get('REDIS_WAIT_INTERVAL', 2))
logger.info(f"Starting Redis availability check at {host}:{port}/{db}")
if wait_for_redis(host, port, db, max_retries, retry_interval):
if wait_for_redis(host, port, db, password, username, max_retries, retry_interval):
sys.exit(0)
else:
sys.exit(1)