Application log timestamps are pinned to UTC no matter what the operator
configures, while nginx in the same container honors TZ - one container,
two timezones (issue 1439). Two mechanisms force this:
1. docker/entrypoint.sh launches the app tree via `su - dispatch`,
which strips the environment; only whitelisted vars are re-exported
through /etc/environment and /etc/profile.d, and TZ is not among
them, so no app process ever sees the operator's declared zone.
2. Whitelisting TZ alone cannot fix Django-managed processes anyway:
Django's settings loader unconditionally re-stamps os.environ["TZ"]
= TIME_ZONE ("UTC") and calls time.tzset() in every uwsgi, celery
and management-command process. Stdlib logging's %(asctime)s renders
via time.localtime(), so it can never show local time in a Django
process regardless of the environment.
The fix, keeping TIME_ZONE = "UTC" / USE_TZ untouched (DB truth and
every wire surface - XMLTV offsets, XC server_info/time_now/EPG
timestamps - stay UTC by design):
- entrypoint: normalize a DISPATCHARR_TIME_ZONE bootstrap var from the
standard TZ env and whitelist it. TZ itself is deliberately NOT
whitelisted: pam_env would hand it to every su- child including
initdb/postgres, silently flipping the database server timezone on
fresh installs - the precondition for the EPG-offset corruption
class tracked in issue 651, and (with an invalid zone) a source of
psycopg "unknown PostgreSQL timezone" warnings emitted while the
connection lock is held. Verified empirically: with TZ whitelisted,
a fresh install ran its sessions at the container zone; without it,
the server default stays UTC.
- settings: capture DISPATCHARR_DISPLAY_TZ at module import, before
Django's tzset re-stamp, as the pre-database display default.
- logging: the verbose formatter renders timestamps with
datetime/ZoneInfo from an in-process cached zone - never
time.localtime(), and never a database query at emit time. An
emit-time query can self-deadlock: a log record that originates
inside a psycopg call (e.g. its timezone warning) would re-enter the
same non-reentrant connection lock the ORM query then needs. The
cache is refreshed out-of-band from provably safe contexts instead:
request_started, celery task_prerun, and a CoreSettings post_save
receiver (immediate in the process that saves the UI setting). The
zone resolves to the UI's System > Time Zone setting - already
canonical for DVR rules, celery crontabs and backup naming - with
the env capture as the value before the first refresh, and any
database error or invalid stored zone keeps the previous value.
- first-boot seed: the fresh-install default that the settings
consolidation migration writes for the system time zone now derives
from the same env capture (validated against zoneinfo, falling back
to UTC) instead of a hardcoded "UTC", so a new install honors the
declared container timezone from first boot. Existing installs are
untouched: the migration only manufactures a default when no stored
value exists, and installs that already ran it never re-run it.
Known residuals: uWSGI's native request log renders its own C-level
ftime outside Python logging and keeps UTC, as do celery's internal
worker_log_format lines; idle workers adopt a changed UI zone on their
next request/task. All documented rather than patched.
- Introduced DATABASE_POOL_CONN_MAX_LIFETIME to manage pooled connection lifecycle, improving performance and resource management.
- Updated database engine path to use 'dispatcharr.db.backends.postgresql_psycopg3' for consistency.
- Enhanced logging configuration for geventpool connection lifecycle tracking.
- Switch engine to postgresql_psycopg3 (PR had wrong engine name,
and we're migrating from psycopg2 to psycopg3 as part of this work)
- Set CONN_MAX_AGE=0 (required by geventpool; PR had None)
- Reduce MAX_CONNS 20→8, REUSE_CONNS 10→3 to avoid exhausting
pg max_connections across 4 uWSGI workers + Celery
- Add pool=False to explicitly disable Django's native psycopg3 pool
Adds native catch-up/timeshift replay for Xtream Codes providers through
the same HTTPStreamReader transport pipeline as live TV.
Timeshift proxy (apps/timeshift/):
- URL cascade: 3 candidate timestamp formats per provider, per-account
format cache for fast-forward seek performance
- MPEG-TS preamble stripping (shared with HTTPStreamReader)
- Stats integration: timeshift viewers appear on /stats with TIMESHIFT badge
- Auth via hmac.compare_digest on XC password
Catchup detection — denormalized for zero-cost output queries:
- Stream.is_catchup + Stream.catchup_days populated at XC import time
- Channel.has_catchup + Channel.catchup_days + Channel.catchup_provider_stream_id
rolled up via ChannelStream post_save signal (UI path) and explicit SQL
after bulk_create (import path)
- _xc_channel_entry() reads denormalized fields instead of per-channel
custom_properties JSON introspection (eliminates N+1 queries)
- Migration 0038 backfills existing data via raw SQL
XC API enhancements:
- server_info.timezone + start/end + time_now use configured timezone
(triple consistency rule — fixes wrong-programme-plays bug)
- Dynamic has_archive flag + auto prev_days for catch-up channels
- XMLTV timestamps rewritten to local timezone for catch-up clients
HTTPStreamReader extended (apps/proxy/live_proxy/input/http_streamer.py):
- 1 MB pipe buffer via fcntl F_SETPIPE_SZ (eliminates producer/consumer
ping-pong that halved throughput)
- Pre-opened response= for URL cascade workflows
- strip_ts_preamble= for XC servers emitting PHP warnings before TS
- find_ts_sync() as shared utility
- Builds on upstream O_NONBLOCK + select() write loop
Provider stream_id lookup order:
- stream_xc() and xc_get_epg() try internal Channel.id first, fall back
to provider stream_id only when needed (avoids unconditional query on
every request)
Also includes:
- VOD provider cascade in stream_vod() — iterates all M3U relations by
priority when first provider is at capacity
- Defensive null-safety: custom_sid: None → "" in get_live_streams,
get_vod_streams, get_vod_info, get_series_info (fixes iPlayTV crash on
JSON null for string fields)
- Timeshift settings UI (timezone selector, debug toggle)
- StreamConnectionCard violet TIMESHIFT badge
- Orphan cleanup skips timeshift_* virtual channels
- Enable gevent cooperative multitasking in all uWSGI worker configs
(gevent-early-monkey-patch + import dispatcharr.gevent_patch)
- Rewrite WebSocket group sends to bypass asyncio in gevent workers:
_gevent_ws_send() replicates the channels_redis 4.x wire format
directly via synchronous Redis so send_websocket_update() and
_send_async() no longer fail silently after epoll is patched out
- Fix PostgreSQL connection exhaustion: CONN_MAX_AGE=0 + explicit
close_old_connections() in stream manager and cleanup watchdog loops
- Fix stream proxy race: register client before the connect-wait loop
so the cleanup watchdog never sees zero clients on a live channel
- Channel list/logo/profile queryset optimisations: conditional DISTINCT,
EXISTS semi-joins for filter-options, channel_count annotation to
eliminate N+1 in LogoSerializer, prefetched memberships in
ChannelProfileSerializer
- JsonResponse for channel ID list and summary endpoints
- Set `DEFAULT_PERMISSION_CLASSES` to `Authenticated` in the DRF configuration.
- Explicitly marked the HDHomeRun discovery endpoints (`DiscoverAPIView`, `LineupAPIView`, `LineupStatusAPIView`, `HDHRDeviceXMLAPIView`) and the version endpoint with `permission_classes = [AllowAny]` to document their intentionally public access now that the global default is `Authenticated`.
Restructure cleanup in refresh_single_m3u_account so del statements
run before gc.collect(), enabling cyclic garbage collection. Add worker_max_memory_per_child as a safety net against memory fragmentation.
Closes#1012, Closes#1053
Add support for authentication when connecting to external Redis instances in modular deployment mode. This enables secure Redis deployments using either password-only authentication (Redis <6) or
username + password authentication (Redis 6+ ACL).
Changes:
- Add REDIS_PASSWORD and REDIS_USER environment variables
- Implement URL encoding for special characters in passwords
- Update all Redis connection points to support auth
- Add comprehensive documentation and examples to docker-compose.yml
- Maintains full backward compatibility (empty defaults = no auth)
All authentication mechanisms have been fully tested including pasword-only authentication, Redis 6+ ACL authentication with username + password, volume-mounted configuration files, and special character handling.
Existing deployments are not effected, authentication support is entirely opt-in using docker-compose.
Also, acknowledging the fact that the docker-compose file for modular deployments has been getting out of hand with auth support, the docker-compose.yml file was re-formatted for better visibility in configuration. This seemed like a better way to go than mandating a .env file.
Real-time notifications for system events and alerts
Per-user notification management and dismissal
Update check on startup and every 24 hours to notify users of available versions
Notification center UI component
Automatic cleanup of expired notifications
- Native Bearer token authentication support in Swagger UI - users can now enter just the JWT token and the "Bearer " prefix is automatically added
- Modern OpenAPI 3.0 specification compliance
- Better auto-generation of request/response schemas
- Improved documentation accuracy with serializer introspection
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
Added ability to use custom comskip.ini
Added series recording without reliance on EPG
Fixed comskip bug
Fixed timezone mismatch when scheduling DVR recordings
No migrations completed yet