diff --git a/.dockerignore b/.dockerignore index e0cc78f0..5073af60 100755 --- a/.dockerignore +++ b/.dockerignore @@ -1,5 +1,6 @@ **/__pycache__ **/.venv +**/venv **/.classpath **/.dockerignore **/.env diff --git a/.gitignore b/.gitignore index b6631ac0..1dd591d4 100755 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ .DS_Store **/__pycache__/ **/.vscode/ +**/venv *.pyc node_modules/ .history/ @@ -10,4 +11,10 @@ docker/Dockerfile DEV static/ data/ .next -next-env.d.ts \ No newline at end of file +next-env.d.ts +media/ +celerybeat-schedule* +dump.rdb +debugpy* +uwsgi.sock +package-lock.json \ No newline at end of file diff --git a/docker/docker-compose.debug.yml b/docker/docker-compose.debug.yml new file mode 100644 index 00000000..40a87bfe --- /dev/null +++ b/docker/docker-compose.debug.yml @@ -0,0 +1,19 @@ +services: + dispatcharr: + # build: + # context: .. + # dockerfile: docker/Dockerfile.dev + image: dispatcharr/dispatcharr + container_name: dispatcharr_debug + ports: + - 5656:5656 # API port + - 9193:9191 # Web UI port + - 8001:8001 # Socket port + - 5678:5678 # Debugging port + volumes: + - ../:/app + environment: + - DISPATCHARR_ENV=dev + - DISPATCHARR_DEBUG=true + - REDIS_HOST=localhost + - CELERY_BROKER_URL=redis://localhost:6379/0 diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh index d04edcb0..1e1cb22f 100755 --- a/docker/entrypoint.sh +++ b/docker/entrypoint.sh @@ -49,6 +49,7 @@ if [[ ! -f /etc/profile.d/dispatcharr.sh ]]; then echo "export POSTGRES_HOST=$POSTGRES_HOST" >> /etc/profile.d/dispatcharr.sh echo "export POSTGRES_PORT=$POSTGRES_PORT" >> /etc/profile.d/dispatcharr.sh echo "export DISPATCHARR_ENV=$DISPATCHARR_ENV" >> /etc/profile.d/dispatcharr.sh + echo "export DISPATCHARR_DEBUG=$DISPATCHARR_DEBUG" >> /etc/profile.d/dispatcharr.sh echo "export REDIS_HOST=$REDIS_HOST" >> /etc/profile.d/dispatcharr.sh echo "export REDIS_DB=$REDIS_DB" >> /etc/profile.d/dispatcharr.sh fi @@ -75,8 +76,18 @@ postgres_pid=$(su - postgres -c "/usr/lib/postgresql/14/bin/pg_ctl -D /data stat echo "✅ Postgres started with PID $postgres_pid" pids+=("$postgres_pid") -if [ "$DISPATCHARR_ENV" = "dev" ]; then + +uwsgi_file="/app/docker/uwsgi.ini" +if [ "$DISPATCHARR_ENV" = "dev" ] && [ "$DISPATCHARR_DEBUG" != "true" ]; then + uwsgi_file="/app/docker/uwsgi.dev.ini" +elif [ "$DISPATCHARR_DEBUG" = "true" ]; then + uwsgi_file="/app/docker/uwsgi.debug.ini" +fi + + +if [[ "$DISPATCHARR_ENV" = "dev" ]]; then . /app/docker/init/99-init-dev.sh + else echo "🚀 Starting nginx..." nginx @@ -85,10 +96,6 @@ else pids+=("$nginx_pid") fi -uwsgi_file="/app/docker/uwsgi.ini" -if [ "$DISPATCHARR_ENV" = "dev" ]; then - uwsgi_file="/app/docker/uwsgi.dev.ini" -fi echo "🚀 Starting uwsgi..." su - $POSTGRES_USER -c "cd /app && uwsgi --ini $uwsgi_file &" @@ -97,7 +104,6 @@ echo "✅ uwsgi started with PID $uwsgi_pid" pids+=("$uwsgi_pid") - cd /app python manage.py migrate --noinput python manage.py collectstatic --noinput diff --git a/docker/init/99-init-dev.sh b/docker/init/99-init-dev.sh index 3e9ecc0a..861c8307 100644 --- a/docker/init/99-init-dev.sh +++ b/docker/init/99-init-dev.sh @@ -15,5 +15,11 @@ fi # Install frontend dependencies cd /app/frontend && npm install - +# Install pip dependencies cd /app && pip install -r requirements.txt + +# Install debugpy for remote debugging +if [ "$DISPATCHARR_DEBUG" = "true" ]; then + echo "=== setting up debugpy ===" + pip install debugpy +fi diff --git a/docker/uwsgi.debug.ini b/docker/uwsgi.debug.ini new file mode 100644 index 00000000..f8df7bdc --- /dev/null +++ b/docker/uwsgi.debug.ini @@ -0,0 +1,81 @@ +[uwsgi] +; exec-before = python manage.py collectstatic --noinput +; exec-before = python manage.py migrate --noinput + +; First run Redis availability check script once +exec-before = python /app/scripts/wait_for_redis.py + +; Start Redis first +attach-daemon = redis-server +; Then start other services +attach-daemon = celery -A dispatcharr worker -l info +attach-daemon = celery -A dispatcharr beat -l info +attach-daemon = daphne -b 0.0.0.0 -p 8001 dispatcharr.asgi:application +attach-daemon = cd /app/frontend && npm run dev + +# Core settings +chdir = /app +module = scripts.debug_wrapper:application +virtualenv = /dispatcharrpy +master = true +env = DJANGO_SETTINGS_MODULE=dispatcharr.settings +socket = /app/uwsgi.sock +chmod-socket = 777 +vacuum = true +die-on-term = true +static-map = /static=/app/static + +# Worker configuration +workers = 1 +threads = 4 +enable-threads = true +lazy-apps = true + +# HTTP server +http = 0.0.0.0:5656 +http-keepalive = 1 +buffer-size = 65536 +http-timeout = 600 + +# Async mode (use gevent for high concurrency) +gevent = 100 +async = 100 + +# Performance tuning +thunder-lock = true +log-4xx = true +log-5xx = true +disable-logging = false + +; Longer timeouts for debugging sessions +harakiri = 3600 +socket-timeout = 3600 +http-timeout = 3600 + + +# Ignore unknown options +ignore-sigpipe = true +ignore-write-errors = true +disable-write-exception = true + +# Explicitly disable for-server option that confuses debugpy +for-server = false + +# Debugging settings +py-autoreload = 1 +honour-stdin = true + +# Environment variables +env = PYTHONPATH=/app +env = PYTHONUNBUFFERED=1 +env = PYDEVD_DISABLE_FILE_VALIDATION=1 +env = PYTHONUTF8=1 +env = PYTHONXOPT=-Xfrozen_modules=off +env = PYDEVD_DEBUG=1 +env = DEBUGPY_LOG_DIR=/app/debugpy_logs + +# Debugging control variables +env = WAIT_FOR_DEBUGGER=false +env = DEBUG_TIMEOUT=30 + + diff --git a/scripts/debug_wrapper.py b/scripts/debug_wrapper.py new file mode 100644 index 00000000..2339fe87 --- /dev/null +++ b/scripts/debug_wrapper.py @@ -0,0 +1,90 @@ +""" +Debug wrapper for the WSGI application. +This module initializes debugpy and then imports the actual application. +""" +import sys +import os +import time +import logging +import inspect + +# Configure logging to output to both console and file +os.makedirs('/app/debugpy_logs', exist_ok=True) +logging.basicConfig( + level=logging.DEBUG, + format='%(asctime)s [%(levelname)s] %(name)s - %(message)s', + handlers=[ + logging.FileHandler('/app/debugpy_logs/debug_wrapper.log'), + logging.StreamHandler(sys.stdout) + ] +) +logger = logging.getLogger('debug_wrapper') + +# Log system info +logger.info(f"Python version: {sys.version}") +logger.info(f"Current directory: {os.getcwd()}") +logger.info(f"Files in current directory: {os.listdir()}") +logger.info(f"Python path: {sys.path}") + +# Default timeout in seconds +DEBUG_TIMEOUT = int(os.environ.get('DEBUG_TIMEOUT', '30')) +# Whether to wait for debugger to attach +WAIT_FOR_DEBUGGER = os.environ.get('WAIT_FOR_DEBUGGER', 'false').lower() == 'true' + +logger.info(f"DEBUG_TIMEOUT: {DEBUG_TIMEOUT}") +logger.info(f"WAIT_FOR_DEBUGGER: {WAIT_FOR_DEBUGGER}") + +try: + import debugpy + from debugpy import configure + logger.info("Successfully imported debugpy") + + # Critical: Configure debugpy to use regular Python for the adapter, not uwsgi + python_path = '/usr/local/bin/python3' + if os.path.exists(python_path): + logger.info(f"Setting debugpy adapter to use Python interpreter: {python_path}") + debugpy.configure(python=python_path) + else: + logger.warning(f"Python path {python_path} not found. Using system default.") + + # Don't wait for connection, just set up the debugging session + logger.info("Initializing debugpy on 0.0.0.0:5678...") + try: + # Use connect instead of listen to avoid the adapter process + debugpy.listen(("0.0.0.0", 5678)) + logger.info("debugpy now listening on 0.0.0.0:5678") + + if WAIT_FOR_DEBUGGER: + logger.info(f"Waiting for debugger to attach (timeout: {DEBUG_TIMEOUT}s)...") + start_time = time.time() + while not debugpy.is_client_connected() and (time.time() - start_time < DEBUG_TIMEOUT): + time.sleep(1) + logger.info("Waiting for debugger connection...") + + if debugpy.is_client_connected(): + logger.info("Debugger attached!") + else: + logger.info(f"Debugger not attached after {DEBUG_TIMEOUT}s, continuing anyway...") + except Exception as e: + logger.error(f"Error with debugpy.listen: {e}", exc_info=True) + logger.info("Continuing without debugging...") + +except ImportError: + logger.error("debugpy not installed, continuing without debugging support") +except Exception as e: + logger.error(f"Failed to initialize debugpy: {e}", exc_info=True) + logger.info("Continuing without debugging support") + +# Now import the actual WSGI application +logger.info("Loading WSGI application...") +try: + from dispatcharr.wsgi import application + logger.info("WSGI application loaded successfully") + + # Log the application details + logger.info(f"Application type: {type(application)}") + logger.info(f"Application callable: {inspect.isfunction(application) or inspect.ismethod(application)}") + +except Exception as e: + logger.error(f"Error loading WSGI application: {e}", exc_info=True) + raise diff --git a/scripts/standalone_debug.py b/scripts/standalone_debug.py new file mode 100644 index 00000000..69558fab --- /dev/null +++ b/scripts/standalone_debug.py @@ -0,0 +1,36 @@ +""" +Standalone debug entry point for the Django application. +This provides a cleaner way to debug without uWSGI complications. + +Run this directly with Python to debug: + python standalone_debug.py +""" +import os +import sys +import debugpy +import logging + +# Configure basic logging +logging.basicConfig( + level=logging.DEBUG, + format='%(asctime)s [%(levelname)s] %(name)s - %(message)s' +) +logger = logging.getLogger('standalone_debug') + +# Setup Django environment +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'dispatcharr.settings') + +# Setup debugpy and wait for connection +logger.info("Setting up debugpy...") +debugpy.listen(("0.0.0.0", 5678)) +logger.info("Waiting for debugger to attach... Connect to 0.0.0.0:5678") +debugpy.wait_for_client() +logger.info("Debugger attached!") + +# Import Django and run the development server +logger.info("Starting Django development server...") +import django +django.setup() + +from django.core.management import execute_from_command_line +execute_from_command_line(['manage.py', 'runserver', '0.0.0.0:8000'])