Merge pull request #881 from CodeBormen/fix-modular-compose-deployment

feat: Add DISPATCHARR_ENV=modular support for multi-container deployment
This commit is contained in:
SergeantPanda 2026-02-03 09:49:07 -06:00 committed by GitHub
commit fb8c831f1b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 396 additions and 150 deletions

View file

@ -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

View file

@ -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:

View file

@ -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

View file

@ -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"

View file

@ -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}" <<EOF
# 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}" <<EOF
DO \$\$
BEGIN
IF NOT EXISTS (SELECT FROM pg_roles WHERE rolname = '$POSTGRES_USER') THEN
@ -126,41 +130,119 @@ BEGIN
END
\$\$;
EOF
echo "Setting PostgreSQL user privileges..."
su postgres -c "$PG_BINDIR/psql -p ${POSTGRES_PORT} -c \"ALTER DATABASE ${POSTGRES_DB} OWNER TO $POSTGRES_USER;\""
su postgres -c "$PG_BINDIR/psql -p ${POSTGRES_PORT} -c \"GRANT ALL PRIVILEGES ON DATABASE ${POSTGRES_DB} TO $POSTGRES_USER;\""
# Finished setting up PosgresSQL database
echo "PostgreSQL database setup complete."
echo "Setting PostgreSQL user privileges..."
su postgres -c "$PG_BINDIR/psql -p ${POSTGRES_PORT} -c \"ALTER DATABASE ${POSTGRES_DB} OWNER TO $POSTGRES_USER;\""
su postgres -c "$PG_BINDIR/psql -p ${POSTGRES_PORT} -c \"GRANT ALL PRIVILEGES ON DATABASE ${POSTGRES_DB} TO $POSTGRES_USER;\""
# Finished setting up PosgresSQL database
echo "PostgreSQL database setup complete."
fi
kill $postgres_pid
while kill -0 $postgres_pid; do
sleep 1
done
fi
kill $postgres_pid
while kill -0 $postgres_pid; do
sleep 1
done
fi
fi # End of DISPATCHARR_ENV != modular check
ensure_utf8_encoding() {
# Check encoding of existing database
CURRENT_ENCODING=$(su - postgres -c "psql -p ${POSTGRES_PORT} -tAc \"SELECT pg_encoding_to_char(encoding) FROM pg_database WHERE datname = '$POSTGRES_DB';\"" | tr -d ' ')
# Supports both internal (Unix socket) and external (TCP) PostgreSQL
echo "Checking database encoding..."
if [[ "$DISPATCHARR_ENV" == "modular" ]]; then
# External database: use TCP connection with password
CURRENT_ENCODING=$(PGPASSWORD="$POSTGRES_PASSWORD" psql -w -h "$POSTGRES_HOST" -p "$POSTGRES_PORT" -U "$POSTGRES_USER" -d "$POSTGRES_DB" -tAc "SELECT pg_encoding_to_char(encoding) FROM pg_database WHERE datname = current_database();" 2>/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
}

59
docker/uwsgi.modular.ini Normal file
View file

@ -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