diff --git a/docker/Dockerfile b/docker/Dockerfile index bfb35c11..3e30a825 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -30,6 +30,12 @@ WORKDIR /app COPY . /app # Copy nginx configuration COPY ./docker/nginx.conf /etc/nginx/sites-enabled/default +# Fix line endings and make entrypoint scripts executable +RUN for f in /app/docker/entrypoint*.sh; do \ + if [ -f "$f" ]; then \ + sed -i 's/\r$//' "$f" && chmod +x "$f"; \ + fi; \ + done # Clean out existing frontend folder RUN rm -rf /app/frontend # Copy built frontend assets diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index e4093e4b..c8e2d53e 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -10,12 +10,14 @@ services: - db - redis environment: + - DISPATCHARR_ENV=modular - POSTGRES_HOST=db + - POSTGRES_PORT=5432 - POSTGRES_DB=dispatcharr - POSTGRES_USER=dispatch - POSTGRES_PASSWORD=secret - REDIS_HOST=redis - - CELERY_BROKER_URL=redis://redis:6379/0 + - REDIS_PORT=6379 - DISPATCHARR_LOG_LEVEL=info # Legacy CPU Support (Optional) # Uncomment to enable legacy NumPy build for older CPUs (circa 2009) @@ -25,7 +27,6 @@ services: # Lower values = higher priority. Range: -20 (highest) to 19 (lowest) # Negative values require cap_add: SYS_NICE (uncomment below) #- UWSGI_NICE_LEVEL=-5 # uWSGI/FFmpeg/Streaming (default: 0, recommended: -5 for high priority) - #- CELERY_NICE_LEVEL=5 # Celery/EPG/Background tasks (default: 5, low priority) # # Uncomment to enable high priority for streaming (required if UWSGI_NICE_LEVEL < 0) #cap_add: @@ -52,25 +53,31 @@ services: depends_on: - db - redis + - web volumes: - - ../:/app + - ./data:/data extra_hosts: - "host.docker.internal:host-gateway" environment: + - DISPATCHARR_ENV=modular - POSTGRES_HOST=db + - POSTGRES_PORT=5432 - POSTGRES_DB=dispatcharr - POSTGRES_USER=dispatch - POSTGRES_PASSWORD=secret - REDIS_HOST=redis - - CELERY_BROKER_URL=redis://redis:6379/0 - command: > - bash -c " - cd /app && - nice -n 5 celery -A dispatcharr worker -l info - " + - REDIS_PORT=6379 + - DISPATCHARR_LOG_LEVEL=info + #- CELERY_NICE_LEVEL=5 #Celery/EPG/Background tasks (default:5, low priority; Range: -20 to 19) + - DJANGO_SETTINGS_MODULE=dispatcharr.settings + - PYTHONUNBUFFERED=1 + # Uncomment to enable high priority for Celery (required if CELERY_NICE_LEVEL < 0) + #cap_add: + # - SYS_NICE + entrypoint: ["/app/docker/entrypoint.celery.sh"] db: - image: postgres:14 + image: postgres:17 container_name: dispatcharr_db ports: - "5436:5432" @@ -80,10 +87,20 @@ services: - POSTGRES_PASSWORD=secret volumes: - postgres_data:/var/lib/postgresql/data + healthcheck: + test: ["CMD-SHELL", "pg_isready -U dispatch -d dispatcharr"] + interval: 5s + timeout: 5s + retries: 5 redis: image: redis:latest container_name: dispatcharr_redis + healthcheck: + test: ["CMD", "redis-cli", "ping"] + interval: 5s + timeout: 5s + retries: 5 volumes: postgres_data: diff --git a/docker/entrypoint.celery.sh b/docker/entrypoint.celery.sh new file mode 100644 index 00000000..81cece86 --- /dev/null +++ b/docker/entrypoint.celery.sh @@ -0,0 +1,29 @@ +#!/bin/bash +set -e + +cd /app +source /dispatcharrpy/bin/activate + +# Wait for Django secret key +echo 'Waiting for Django secret key...' +while [ ! -f /data/jwt ]; do sleep 1; done +export DJANGO_SECRET_KEY="$(tr -d '\r\n' < /data/jwt)" + +# Wait for migrations to complete (check that NO unapplied migrations remain) +echo 'Waiting for migrations to complete...' +until ! python manage.py showmigrations 2>&1 | grep -q '\[ \]'; do + echo 'Migrations not ready yet, waiting...' + sleep 2 +done + +# Start Celery +echo 'Migrations complete, starting Celery...' +celery -A dispatcharr beat -l info & + +# Default to nice level 5 (lower priority) - safe for unprivileged containers +# Negative values require SYS_NICE capability +NICE_LEVEL="${CELERY_NICE_LEVEL:-5}" +if [ "$NICE_LEVEL" -lt 0 ] 2>/dev/null; then + echo "Warning: CELERY_NICE_LEVEL=$NICE_LEVEL is negative, requires SYS_NICE capability" +fi +nice -n "$NICE_LEVEL" celery -A dispatcharr worker -l info --autoscale=6,1 diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh index 1594385f..ead6aadd 100755 --- a/docker/entrypoint.sh +++ b/docker/entrypoint.sh @@ -36,6 +36,7 @@ export POSTGRES_PORT=${POSTGRES_PORT:-5432} export PG_VERSION=$(ls /usr/lib/postgresql/ | sort -V | tail -n 1) 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 DISPATCHARR_PORT=${DISPATCHARR_PORT:-9191} export LIBVA_DRIVERS_PATH='/usr/local/lib/x86_64-linux-gnu/dri' @@ -54,7 +55,7 @@ PY mv -f "$tmpfile" "$SECRET_FILE" || { echo "move failed"; rm -f "$tmpfile"; exit 1; } umask $old_umask fi -export DJANGO_SECRET_KEY="$(cat "$SECRET_FILE")" +export DJANGO_SECRET_KEY="$(tr -d '\r\n' < "$SECRET_FILE")" # Process priority configuration # UWSGI_NICE_LEVEL: Absolute nice value for uWSGI/streaming (default: 0 = normal priority) @@ -103,7 +104,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_DB POSTGRES_DIR DISPATCHARR_PORT + REDIS_HOST REDIS_PORT REDIS_DB 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 ) @@ -138,25 +139,74 @@ fi # Run init scripts echo "Starting user setup..." . /app/docker/init/01-user-setup.sh + +# Initialize PostgreSQL (script handles modular vs internal mode internally) echo "Setting up PostgreSQL..." . /app/docker/init/02-postgres.sh + echo "Starting init process..." . /app/docker/init/03-init-dispatcharr.sh -# Start PostgreSQL -echo "Starting Postgres..." -su - postgres -c "$PG_BINDIR/pg_ctl -D ${POSTGRES_DIR} start -w -t 300 -o '-c port=${POSTGRES_PORT}'" -# Wait for PostgreSQL to be ready -until su - postgres -c "$PG_BINDIR/pg_isready -h ${POSTGRES_HOST} -p ${POSTGRES_PORT}" >/dev/null 2>&1; do - echo_with_timestamp "Waiting for PostgreSQL to be ready..." - sleep 1 -done -postgres_pid=$(su - postgres -c "$PG_BINDIR/pg_ctl -D ${POSTGRES_DIR} status" | sed -n 's/.*PID: \([0-9]\+\).*/\1/p') -echo "✅ Postgres started with PID $postgres_pid" -pids+=("$postgres_pid") +# Start PostgreSQL if NOT in modular mode (using external database) +if [[ "$DISPATCHARR_ENV" != "modular" ]]; then + echo "Starting Postgres..." + su - postgres -c "$PG_BINDIR/pg_ctl -D ${POSTGRES_DIR} start -w -t 300 -o '-c port=${POSTGRES_PORT}'" + # Wait for PostgreSQL to be ready + until su - postgres -c "$PG_BINDIR/pg_isready -h ${POSTGRES_HOST} -p ${POSTGRES_PORT}" >/dev/null 2>&1; do + echo_with_timestamp "Waiting for PostgreSQL to be ready..." + sleep 1 + done + postgres_pid=$(su - postgres -c "$PG_BINDIR/pg_ctl -D ${POSTGRES_DIR} status" | sed -n 's/.*PID: \([0-9]\+\).*/\1/p') + echo "✅ Postgres started with PID $postgres_pid" + pids+=("$postgres_pid") +else + echo "🔗 Modular mode: Using external PostgreSQL at ${POSTGRES_HOST}:${POSTGRES_PORT}" + # Wait for external PostgreSQL to be ready using Python (no pg_isready needed) + echo_with_timestamp "Waiting for external PostgreSQL to be ready..." + until python3 -c " +import socket +import sys +try: + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + s.settimeout(2) + s.connect(('${POSTGRES_HOST}', ${POSTGRES_PORT})) + s.close() + sys.exit(0) +except Exception: + sys.exit(1) +" 2>/dev/null; do + echo_with_timestamp "Waiting for PostgreSQL at ${POSTGRES_HOST}:${POSTGRES_PORT}..." + sleep 1 + done + echo "✅ External PostgreSQL is ready" -# Ensure database encoding is UTF8 -. /app/docker/init/02-postgres.sh + # Check PostgreSQL version compatibility + check_external_postgres_version || exit 1 +fi + +# Wait for Redis to be ready (modular mode uses external Redis) +if [[ "$DISPATCHARR_ENV" == "modular" ]]; then + echo "🔗 Modular mode: Using external Redis at ${REDIS_HOST}:${REDIS_PORT}" + echo_with_timestamp "Waiting for external Redis to be ready..." + until python3 -c " +import socket +import sys +try: + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + s.settimeout(2) + s.connect(('${REDIS_HOST}', ${REDIS_PORT})) + s.close() + sys.exit(0) +except Exception: + sys.exit(1) +" 2>/dev/null; do + echo_with_timestamp "Waiting for Redis at ${REDIS_HOST}:${REDIS_PORT}..." + sleep 1 + done + echo "✅ External Redis is ready" +fi + +# Ensure database encoding is UTF8 (handles both internal and external databases) ensure_utf8_encoding if [[ "$DISPATCHARR_ENV" = "dev" ]]; then @@ -198,6 +248,9 @@ if [ "$DISPATCHARR_ENV" = "dev" ] && [ "$DISPATCHARR_DEBUG" != "true" ]; then elif [ "$DISPATCHARR_DEBUG" = "true" ]; then echo "🚀 Starting uwsgi in debug mode..." uwsgi_file="/app/docker/uwsgi.debug.ini" +elif [ "$DISPATCHARR_ENV" = "modular" ]; then + echo "🚀 Starting uwsgi in modular mode..." + uwsgi_file="/app/docker/uwsgi.modular.ini" else echo "🚀 Starting uwsgi in production mode..." uwsgi_file="/app/docker/uwsgi.ini" diff --git a/docker/init/02-postgres.sh b/docker/init/02-postgres.sh index e36dd744..87aa94ef 100644 --- a/docker/init/02-postgres.sh +++ b/docker/init/02-postgres.sh @@ -1,123 +1,127 @@ #!/bin/bash -# Temporary migration from postgres in /data to $POSTGRES_DIR. Can likely remove -# some time in the future. -if [ -e "/data/postgresql.conf" ]; then - echo "Migrating PostgreSQL data from /data to $POSTGRES_DIR..." - # Create a temporary directory outside of /data - mkdir -p /tmp/postgres_migration +# Skip internal PostgreSQL setup in modular mode (using external database) +if [[ "$DISPATCHARR_ENV" != "modular" ]]; then - # Move the PostgreSQL files to the temporary directory - mv /data/* /tmp/postgres_migration/ + # Temporary migration from postgres in /data to $POSTGRES_DIR. Can likely remove + # some time in the future. + if [ -e "/data/postgresql.conf" ]; then + echo "Migrating PostgreSQL data from /data to $POSTGRES_DIR..." - # Create the target directory - mkdir -p $POSTGRES_DIR + # Create a temporary directory outside of /data + mkdir -p /tmp/postgres_migration - # Move the files from temporary directory to the final location - mv /tmp/postgres_migration/* $POSTGRES_DIR/ + # Move the PostgreSQL files to the temporary directory + mv /data/* /tmp/postgres_migration/ - # Clean up the temporary directory - rmdir /tmp/postgres_migration + # Create the target directory + mkdir -p $POSTGRES_DIR - # Set proper ownership and permissions for PostgreSQL data directory - chown -R postgres:postgres $POSTGRES_DIR - chmod 700 $POSTGRES_DIR + # Move the files from temporary directory to the final location + mv /tmp/postgres_migration/* $POSTGRES_DIR/ - echo "Migration completed successfully." -fi + # Clean up the temporary directory + rmdir /tmp/postgres_migration -PG_VERSION_FILE="${POSTGRES_DIR}/PG_VERSION" + # Set proper ownership and permissions for PostgreSQL data directory + chown -R postgres:postgres $POSTGRES_DIR + chmod 700 $POSTGRES_DIR -# Detect current version from data directory, if present -if [ -f "$PG_VERSION_FILE" ]; then - CURRENT_VERSION=$(cat "$PG_VERSION_FILE") -else - CURRENT_VERSION="" -fi + echo "Migration completed successfully." + fi -# Only run upgrade if current version is set and not the target -if [ -n "$CURRENT_VERSION" ] && [ "$CURRENT_VERSION" != "$PG_VERSION" ]; then - echo "Detected PostgreSQL data directory version $CURRENT_VERSION, upgrading to $PG_VERSION..." - # Set binary paths for upgrade if needed - OLD_BINDIR="/usr/lib/postgresql/${CURRENT_VERSION}/bin" - NEW_BINDIR="/usr/lib/postgresql/${PG_VERSION}/bin" - PG_INSTALLED_BY_SCRIPT=0 - if [ ! -d "$OLD_BINDIR" ]; then - echo "PostgreSQL binaries for version $CURRENT_VERSION not found. Installing..." - apt update && apt install -y postgresql-$CURRENT_VERSION postgresql-contrib-$CURRENT_VERSION - if [ $? -ne 0 ]; then - echo "Failed to install PostgreSQL version $CURRENT_VERSION. Exiting." - exit 1 + PG_VERSION_FILE="${POSTGRES_DIR}/PG_VERSION" + + # Detect current version from data directory, if present + if [ -f "$PG_VERSION_FILE" ]; then + CURRENT_VERSION=$(cat "$PG_VERSION_FILE") + else + CURRENT_VERSION="" + fi + + # Only run upgrade if current version is set and not the target + if [ -n "$CURRENT_VERSION" ] && [ "$CURRENT_VERSION" != "$PG_VERSION" ]; then + echo "Detected PostgreSQL data directory version $CURRENT_VERSION, upgrading to $PG_VERSION..." + # Set binary paths for upgrade if needed + OLD_BINDIR="/usr/lib/postgresql/${CURRENT_VERSION}/bin" + NEW_BINDIR="/usr/lib/postgresql/${PG_VERSION}/bin" + PG_INSTALLED_BY_SCRIPT=0 + if [ ! -d "$OLD_BINDIR" ]; then + echo "PostgreSQL binaries for version $CURRENT_VERSION not found. Installing..." + apt update && apt install -y postgresql-$CURRENT_VERSION postgresql-contrib-$CURRENT_VERSION + if [ $? -ne 0 ]; then + echo "Failed to install PostgreSQL version $CURRENT_VERSION. Exiting." + exit 1 + fi + PG_INSTALLED_BY_SCRIPT=1 + fi + + # Prepare new data directory + NEW_POSTGRES_DIR="${POSTGRES_DIR}_$PG_VERSION" + + # Remove new data directory if it already exists (from a failed/partial upgrade) + if [ -d "$NEW_POSTGRES_DIR" ]; then + echo "Warning: $NEW_POSTGRES_DIR already exists. Removing it to avoid upgrade issues." + rm -rf "$NEW_POSTGRES_DIR" + fi + + mkdir -p "$NEW_POSTGRES_DIR" + chown -R postgres:postgres "$NEW_POSTGRES_DIR" + chmod 700 "$NEW_POSTGRES_DIR" + + # Initialize new data directory + echo "Initializing new PostgreSQL data directory at $NEW_POSTGRES_DIR..." + su - postgres -c "$NEW_BINDIR/initdb -D $NEW_POSTGRES_DIR" + echo "Running pg_upgrade from $OLD_BINDIR to $NEW_BINDIR..." + # Run pg_upgrade + su - postgres -c "$NEW_BINDIR/pg_upgrade -b $OLD_BINDIR -B $NEW_BINDIR -d $POSTGRES_DIR -D $NEW_POSTGRES_DIR" + + # Move old data directory for backup, move new into place + mv "$POSTGRES_DIR" "${POSTGRES_DIR}_backup_${CURRENT_VERSION}_$(date +%s)" + mv "$NEW_POSTGRES_DIR" "$POSTGRES_DIR" + + echo "Upgrade complete. Old data directory backed up." + + # Uninstall PostgreSQL if we installed it just for upgrade + if [ "$PG_INSTALLED_BY_SCRIPT" -eq 1 ]; then + echo "Uninstalling temporary PostgreSQL $CURRENT_VERSION packages..." + apt remove -y postgresql-$CURRENT_VERSION postgresql-contrib-$CURRENT_VERSION + apt autoremove -y fi - PG_INSTALLED_BY_SCRIPT=1 fi - # Prepare new data directory - NEW_POSTGRES_DIR="${POSTGRES_DIR}_$PG_VERSION" + # Initialize PostgreSQL database + if [ -z "$(ls -A $POSTGRES_DIR)" ]; then + echo "Initializing PostgreSQL database..." + mkdir -p $POSTGRES_DIR + chown -R postgres:postgres $POSTGRES_DIR + chmod 700 $POSTGRES_DIR - # Remove new data directory if it already exists (from a failed/partial upgrade) - if [ -d "$NEW_POSTGRES_DIR" ]; then - echo "Warning: $NEW_POSTGRES_DIR already exists. Removing it to avoid upgrade issues." - rm -rf "$NEW_POSTGRES_DIR" - fi + # Initialize PostgreSQL + su - postgres -c "$PG_BINDIR/initdb -D ${POSTGRES_DIR}" + # Configure PostgreSQL + echo "host all all 0.0.0.0/0 md5" >> "${POSTGRES_DIR}/pg_hba.conf" + echo "listen_addresses='*'" >> "${POSTGRES_DIR}/postgresql.conf" - mkdir -p "$NEW_POSTGRES_DIR" - chown -R postgres:postgres "$NEW_POSTGRES_DIR" - chmod 700 "$NEW_POSTGRES_DIR" + # Start PostgreSQL + echo "Starting Postgres..." + su - postgres -c "$PG_BINDIR/pg_ctl -D ${POSTGRES_DIR} start -w -t 300 -o '-c port=${POSTGRES_PORT}'" + # Wait for PostgreSQL to be ready + until su - postgres -c "$PG_BINDIR/pg_isready -h ${POSTGRES_HOST} -p ${POSTGRES_PORT}" >/dev/null 2>&1; do + echo "Waiting for PostgreSQL to be ready..." + sleep 1 + done - # Initialize new data directory - echo "Initializing new PostgreSQL data directory at $NEW_POSTGRES_DIR..." - su - postgres -c "$NEW_BINDIR/initdb -D $NEW_POSTGRES_DIR" - echo "Running pg_upgrade from $OLD_BINDIR to $NEW_BINDIR..." - # Run pg_upgrade - su - postgres -c "$NEW_BINDIR/pg_upgrade -b $OLD_BINDIR -B $NEW_BINDIR -d $POSTGRES_DIR -D $NEW_POSTGRES_DIR" + postgres_pid=$(su - postgres -c "$PG_BINDIR/pg_ctl -D ${POSTGRES_DIR} status" | sed -n 's/.*PID: \([0-9]\+\).*/\1/p') - # Move old data directory for backup, move new into place - mv "$POSTGRES_DIR" "${POSTGRES_DIR}_backup_${CURRENT_VERSION}_$(date +%s)" - mv "$NEW_POSTGRES_DIR" "$POSTGRES_DIR" - - echo "Upgrade complete. Old data directory backed up." - - # Uninstall PostgreSQL if we installed it just for upgrade - if [ "$PG_INSTALLED_BY_SCRIPT" -eq 1 ]; then - echo "Uninstalling temporary PostgreSQL $CURRENT_VERSION packages..." - apt remove -y postgresql-$CURRENT_VERSION postgresql-contrib-$CURRENT_VERSION - apt autoremove -y - fi -fi - -# Initialize PostgreSQL database -if [ -z "$(ls -A $POSTGRES_DIR)" ]; then - echo "Initializing PostgreSQL database..." - mkdir -p $POSTGRES_DIR - chown -R postgres:postgres $POSTGRES_DIR - chmod 700 $POSTGRES_DIR - - # Initialize PostgreSQL - su - postgres -c "$PG_BINDIR/initdb -D ${POSTGRES_DIR}" - # Configure PostgreSQL - echo "host all all 0.0.0.0/0 md5" >> "${POSTGRES_DIR}/pg_hba.conf" - echo "listen_addresses='*'" >> "${POSTGRES_DIR}/postgresql.conf" - - # Start PostgreSQL - echo "Starting Postgres..." - su - postgres -c "$PG_BINDIR/pg_ctl -D ${POSTGRES_DIR} start -w -t 300 -o '-c port=${POSTGRES_PORT}'" - # Wait for PostgreSQL to be ready - until su - postgres -c "$PG_BINDIR/pg_isready -h ${POSTGRES_HOST} -p ${POSTGRES_PORT}" >/dev/null 2>&1; do - echo "Waiting for PostgreSQL to be ready..." - sleep 1 - done - - postgres_pid=$(su - postgres -c "$PG_BINDIR/pg_ctl -D ${POSTGRES_DIR} status" | sed -n 's/.*PID: \([0-9]\+\).*/\1/p') - - # Setup database if needed - if ! su - postgres -c "psql -p ${POSTGRES_PORT} -tAc \"SELECT 1 FROM pg_database WHERE datname = '$POSTGRES_DB';\"" | grep -q 1; then - # Create PostgreSQL database - echo "Creating PostgreSQL database..." - su - postgres -c "createdb -p ${POSTGRES_PORT} --encoding=UTF8 ${POSTGRES_DB}" - # Create user, set ownership, and grant privileges - echo "Creating PostgreSQL user..." - su - postgres -c "psql -p ${POSTGRES_PORT} -d ${POSTGRES_DB}" </dev/null | tr -d ' ') + else + # Internal database: use Unix socket as postgres user + CURRENT_ENCODING=$(su - postgres -c "psql -p ${POSTGRES_PORT} -d ${POSTGRES_DB} -tAc \"SELECT pg_encoding_to_char(encoding) FROM pg_database WHERE datname = current_database();\"" | tr -d ' ') + fi + if [ "$CURRENT_ENCODING" != "UTF8" ]; then echo "Database $POSTGRES_DB encoding is $CURRENT_ENCODING, converting to UTF8..." DUMP_FILE="/tmp/${POSTGRES_DB}_utf8_dump_$(date +%s).sql" - # Dump database (include permissions and ownership) - su - postgres -c "pg_dump -p ${POSTGRES_PORT} $POSTGRES_DB > $DUMP_FILE" - # Drop and recreate database with UTF8 encoding using template0 - su - postgres -c "dropdb -p ${POSTGRES_PORT} $POSTGRES_DB" - # Recreate database with UTF8 encoding - su - postgres -c "createdb -p ${POSTGRES_PORT} --encoding=UTF8 --template=template0 ${POSTGRES_DB}" - - - # Restore data - su - postgres -c "psql -p ${POSTGRES_PORT} -d $POSTGRES_DB < $DUMP_FILE" - #configure_db + if [[ "$DISPATCHARR_ENV" == "modular" ]]; then + # External database: use TCP connection with password + # Dump database (include permissions and ownership) + PGPASSWORD="$POSTGRES_PASSWORD" pg_dump -w -h "$POSTGRES_HOST" -p "$POSTGRES_PORT" -U "$POSTGRES_USER" "$POSTGRES_DB" > "$DUMP_FILE" || { echo "Dump failed"; return 1; } + # Drop and recreate database with UTF8 encoding using template0 + PGPASSWORD="$POSTGRES_PASSWORD" dropdb -w -h "$POSTGRES_HOST" -p "$POSTGRES_PORT" -U "$POSTGRES_USER" "$POSTGRES_DB" || { echo "Drop failed"; return 1; } + # Recreate database with UTF8 encoding + PGPASSWORD="$POSTGRES_PASSWORD" createdb -w -h "$POSTGRES_HOST" -p "$POSTGRES_PORT" -U "$POSTGRES_USER" --encoding=UTF8 --template=template0 "$POSTGRES_DB" || { echo "Create failed"; return 1; } + # Restore data + PGPASSWORD="$POSTGRES_PASSWORD" psql -w -h "$POSTGRES_HOST" -p "$POSTGRES_PORT" -U "$POSTGRES_USER" -d "$POSTGRES_DB" < "$DUMP_FILE" || { echo "Restore failed"; return 1; } + else + # Internal database: use Unix socket as postgres user + # Dump database (include permissions and ownership) + su - postgres -c "pg_dump -p ${POSTGRES_PORT} ${POSTGRES_DB}" > "$DUMP_FILE" || { echo "Dump failed"; return 1; } + # Drop and recreate database with UTF8 encoding using template0 + su - postgres -c "dropdb -p ${POSTGRES_PORT} ${POSTGRES_DB}" || { echo "Drop failed"; return 1; } + # Recreate database with UTF8 encoding and correct owner + su - postgres -c "createdb -p ${POSTGRES_PORT} --encoding=UTF8 --template=template0 --owner=${POSTGRES_USER} ${POSTGRES_DB}" || { echo "Create failed"; return 1; } + # Restore data + cat "$DUMP_FILE" | su - postgres -c "psql -p ${POSTGRES_PORT} -d ${POSTGRES_DB}" || { echo "Restore failed"; return 1; } + fi rm -f "$DUMP_FILE" - echo "Database $POSTGRES_DB converted to UTF8 and permissions set." + echo "✅ Database $POSTGRES_DB converted to UTF8." + else + echo "✅ Database encoding is UTF8" fi } +check_external_postgres_version() { + # Only check for modular deployments + if [[ "$DISPATCHARR_ENV" != "modular" ]]; then + return 0 + fi + + echo "🔍 Checking external PostgreSQL version compatibility..." + + # Get minimum required version from base image (set in entrypoint.sh) + # PG_VERSION is from DispatcharrBase + MIN_REQUIRED_VERSION=$PG_VERSION + + # Query external PostgreSQL version + EXTERNAL_VERSION=$(PGPASSWORD="$POSTGRES_PASSWORD" psql -w -h "$POSTGRES_HOST" -p "$POSTGRES_PORT" -U "$POSTGRES_USER" -d "postgres" -tAc "SHOW server_version;" 2>/dev/null | grep -oE '^[0-9]+') + + if [ -z "$EXTERNAL_VERSION" ]; then + echo "❌ ERROR: Unable to determine external PostgreSQL version" + echo " Could not connect to database at ${POSTGRES_HOST}:${POSTGRES_PORT}" + echo " Please verify your database connection settings." + return 1 + fi + + # Compare versions + if [[ "$EXTERNAL_VERSION" -lt "$MIN_REQUIRED_VERSION" ]]; then + # FAIL: Version too old + echo "" + echo "❌ ERROR: PostgreSQL version mismatch" + echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + echo " External Database: PostgreSQL $EXTERNAL_VERSION" + echo " Required Version: PostgreSQL $MIN_REQUIRED_VERSION or higher" + echo "" + echo " Your external PostgreSQL database is too old for Dispatcharr." + echo " Please upgrade to PostgreSQL $MIN_REQUIRED_VERSION or higher." + echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + echo "" + return 1 + + elif [[ "$EXTERNAL_VERSION" -eq "$MIN_REQUIRED_VERSION" ]]; then + # MATCH: Exact version match + echo "✅ PostgreSQL version check passed" + echo " External Database: PostgreSQL $EXTERNAL_VERSION (matches target version)" + + else + # HIGHER: Newer version + echo "✅ PostgreSQL version check passed" + echo " External Database: PostgreSQL $EXTERNAL_VERSION" + echo " Target Version: PostgreSQL $MIN_REQUIRED_VERSION" + echo " â„šī¸ Your database is newer than the target version." + echo " PostgreSQL version should be compatible with Dispatcharr." + fi + + return 0 +} + diff --git a/docker/uwsgi.modular.ini b/docker/uwsgi.modular.ini new file mode 100644 index 00000000..3220a8d8 --- /dev/null +++ b/docker/uwsgi.modular.ini @@ -0,0 +1,59 @@ +[uwsgi] +; Modular deployment mode - external PostgreSQL, Redis, and Celery +; Remove file creation commands since we're not logging to files anymore +; exec-pre = mkdir -p /data/logs +; exec-pre = touch /data/logs/uwsgi.log +; exec-pre = chmod 666 /data/logs/uwsgi.log + +; First run Redis availability check script once +exec-pre = python /app/scripts/wait_for_redis.py + +; Start Daphne for WebSocket support (required for real-time features) +; Redis and Celery run in separate containers in modular mode +attach-daemon = daphne -b 0.0.0.0 -p 8001 dispatcharr.asgi:application + +# Core settings +chdir = /app +module = dispatcharr.wsgi:application +virtualenv = /dispatcharrpy +master = true +env = DJANGO_SETTINGS_MODULE=dispatcharr.settings +env = USE_NGINX_ACCEL=true +socket = /app/uwsgi.sock +chmod-socket = 777 +vacuum = true +die-on-term = true +static-map = /static=/app/static + +# Worker management +workers = 4 + +# Optimize for streaming +http = 0.0.0.0:5656 +http-keepalive = 1 +buffer-size = 65536 # Increase buffer for large payloads +post-buffering = 4096 # Reduce buffering for real-time streaming +http-timeout = 600 # Prevent disconnects from long streams +socket-timeout = 600 # Prevent write timeouts when client buffers +lazy-apps = true # Improve memory efficiency + +# Async mode (use gevent for high concurrency) +gevent = 400 # Each unused greenlet costs ~2-4KB of memory +# Higher values have minimal performance impact when idle, but provide capacity for traffic spikes +# If memory usage becomes an issue, reduce this value + +# Performance tuning +thunder-lock = true +log-4xx = true +log-5xx = true +disable-logging = false + +# Logging configuration +# Enable console logging (stdout) +log-master = true +# Enable strftime formatting for timestamps +logformat-strftime = true +log-date = %%Y-%%m-%%d %%H:%%M:%%S,000 +# Use formatted time with environment variable for log level +log-format = %(ftime) $(DISPATCHARR_LOG_LEVEL) uwsgi.requests Worker ID: %(wid) %(method) %(status) %(uri) %(msecs)ms +log-buffering = 1024 # Add buffer size limit for logging