From 29f02f98314553d1827ed209c1c4c0690a041f76 Mon Sep 17 00:00:00 2001 From: TrueCharts Bot Date: Wed, 18 Mar 2026 04:39:40 +0100 Subject: [PATCH] =?UTF-8?q?feat(dispatcharr):=20update=20image=20docker.io?= =?UTF-8?q?/dispatcharr/dispatcharr=200.20.2=20=E2=86=92=200.21.1=20(#4616?= =?UTF-8?q?4)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This PR contains the following updates: | Package | Update | Change | |---|---|---| | [docker.io/dispatcharr/dispatcharr](https://redirect.github.com/Dispatcharr/Dispatcharr) | minor | `116f7f4` → `3fe4da0` | --- > [!WARNING] > Some dependencies could not be looked up. Check the [Dependency Dashboard](../issues/18710) for more information. Add the preset `:preserveSemverRanges` to your config if you don't want to pin your dependencies. --- ### Release Notes
Dispatcharr/Dispatcharr (docker.io/dispatcharr/dispatcharr) ### [`v0.21.1`](https://redirect.github.com/Dispatcharr/Dispatcharr/blob/HEAD/CHANGELOG.md#0211---2026-03-18) [Compare Source](https://redirect.github.com/Dispatcharr/Dispatcharr/compare/v0.21.0...v0.21.1) ##### Fixed - Docker container initialization fixes for PUID/PGID handling — Thanks [@​CodeBormen](https://redirect.github.com/CodeBormen): - Backups failing on previous installations where `/data/backups` already existed: `/data/backups` was missing from the `DATA_DIRS` list in the init script, causing the PUID/PGID ownership migration to skip the directory and leave it with incorrect permissions. - Container startup failure on upgrade when data directories reside on external mounts (NFS, SMB/CIFS, FUSE): `chown` failures under `set -e` were crashing the container, breaking setups that worked fine on the previous image. Failures are now collected per-directory and reported as a consolidated warning; the container continues to start and Django reports at runtime if it cannot write a specific directory. - Upgrading users running as UID 102 (the internal PostgreSQL system user) instead of the expected UID 1000: the PUID/PGID auto-detect introduced in v0.21.0 read ownership from `/data/db`, which was UID 102 in pre-PUID images, causing Django, file creation, and comskip to all run as the wrong user. PUID/PGID now default to 1000 (matching the original Django UID) rather than auto-detecting from data directory ownership. ### [`v0.21.0`](https://redirect.github.com/Dispatcharr/Dispatcharr/blob/HEAD/CHANGELOG.md#0210---2026-03-17) [Compare Source](https://redirect.github.com/Dispatcharr/Dispatcharr/compare/v0.20.2...v0.21.0) ##### Security - Updated frontend npm dependencies to resolve 1 high-severity vulnerability: - Updated `flatted` to 3.4.1, resolving **high** unbounded recursion DoS in the `parse()` revive phase ([GHSA-25h7-pfq9-p65f](https://redirect.github.com/advisories/GHSA-25h7-pfq9-p65f)) - Updated `Django` to 6.0.3 and `django-celery-beat` to 2.9.0, resolving new security vulnerabilities: - [CVE-2026-25673](https://www.cve.org/CVERecord?id=CVE-2026-25673): Potential denial-of-service vulnerability in URLField via Unicode normalization on Windows (March 3, 2026) - [CVE-2026-25674](https://www.cve.org/CVERecord?id=CVE-2026-25674): Potential incorrect permissions on newly created file system objects (March 3, 2026) ##### Added - Configurable sidebar navigation ordering and visibility — Thanks [@​jcasimir](https://redirect.github.com/jcasimir) - Sidebar nav items can be reordered via drag-and-drop in Settings → UI Settings → Navigation. - Individual nav items can be hidden from the sidebar using the eye toggle. Hiding an item preserves its position in the order. - A "Reset to Default" button restores the role-appropriate default order and clears all hidden items. - Order and visibility are saved per-user with optimistic updates and automatic rollback on failure. Changes appear in the sidebar immediately without a page reload. - Admin users see a grouped navigation: flat items (`Channels`, `VODs`, `M3U & EPG Manager`, `TV Guide`, `DVR`, `Stats`, `Plugins`) plus collapsible `Integrations` (Connections, Logs) and `System` (Users, Logo Manager, Settings) groups. The `System` group cannot be hidden. - Non-admin users see `Channels`, `TV Guide`, and `Settings`, with the `Settings` item not hideable. - Unit tests for `NotificationCenter`, `NotificationCenterUtils`, and `M3URefreshNotification` components, and for settings form components `DvrSettingsForm`, `NetworkAccessForm`, `ProxySettingsForm`, `StreamSettingsForm`, `SystemSettingsForm`, `UiSettingsForm`. — Thanks [@​nick4810](https://redirect.github.com/nick4810) - Unit tests for DVR port resolution (`build_dvr_candidates`) and selective Redis flush behavior in modular mode. — Thanks [@​CodeBormen](https://redirect.github.com/CodeBormen) - Floating video player improvements - **Title display**: The channel, stream, or VOD title is now shown in the player header bar. Title is passed through from all preview entry points: channel table, stream table, stream connection card, guide, DVR, recording cards, recording details modal, VOD modal, and series modal. - **Persistent state**: Size, position, volume level, and mute state are now saved across sessions using a single `dispatcharr-player-prefs` localStorage key. Size and position are restored on next open (clamped to the current viewport); volume and mute are restored when the player initialises. - New Client Buffer proxy setting: new clients joining an active channel are now positioned a configurable number of seconds behind live rather than a fixed chunk count. The start position is determined by wall-clock chunk receive time (stored as a Redis sorted set alongside the buffer), so the buffer depth is consistent in seconds regardless of stream bitrate. Setting the value to `0` starts clients at live with no buffer. Defaults to 5 seconds. Existing chunk-count gating for the first client connecting to a channel is unchanged. The setting is exposed in Settings → Proxy as "New Client Buffer (seconds)". - Channel table filter for channels that have stale streams: A new "Has Stale Streams" filter option in the channel table header menu highlights and filters channels containing at least one stale stream. Channels with stale streams are visually distinguished with an orange tint. The filter is mutually exclusive with "Only Empty Channels". - Thanks [@​JCBird1012](https://redirect.github.com/JCBird1012) - "Next Highest Channel" numbering mode when creating channels from streams: A new `Next Highest` option is available alongside `Provider`, `Auto`, and `Custom` when creating channels from the Streams table. Selecting it assigns channel numbers starting one above the current highest channel number; the next available number is fetched from the backend at selection time. (Closes [#​1000](https://redirect.github.com/Dispatcharr/Dispatcharr/issues/1000)) — Thanks [@​JCBird1012](https://redirect.github.com/JCBird1012) - TV Guide program cards now display richer metadata — Thanks [@​CodeBormen](https://redirect.github.com/CodeBormen) - **Season/episode badges** (e.g. `S12E06`) extracted from EPG `` elements, `onscreen` episode strings (e.g. `S12 E6`, `S3E21`, `S8 E8 P2/2`), and as a last-resort fallback from description text patterns at parse time (3-tier pipeline). (Closes [#​1065](https://redirect.github.com/Dispatcharr/Dispatcharr/issues/1065)) - **Episode subtitle** shown below the program title on guide cards; falls back to the short description when no subtitle is available. - **Status badges**: `LIVE`, `NEW`, `PREMIERE`, and `FINALE` surfaced from EPG flags on both compact cards and the detail modal. - **Program detail modal**: Clicking any guide program opens a modal with full program details — poster/icon image, season/episode, subtitle, duration, categories, cast/director/writer credits, content rating, star ratings, production date, original air date, video quality, and external links to IMDb and TMDB where available. Detail data is fetched from a new `GET /api/epg/programs/{id}/` endpoint backed by the new `ProgramDetailSerializer`. Dummy/placeholder programs skip the fetch. - **Real-time progress bars**: Currently-airing programs show a green progress bar on their guide card that updates every second via direct DOM manipulation (no React re-renders). - **Channel name tooltip**: Hovering the channel logo column shows the channel name. - Sort icons added to the Group and EPG column headers in the Channels table, and to the Group column header in the Streams table. Clicking a sort icon cycles through ascending/descending/unsorted states. EPG sorting required a backend change (`epg_data__name` added to `ChannelViewSet.ordering_fields`); Group sorting was already supported by the API in both tables. (Closes [#​854](https://redirect.github.com/Dispatcharr/Dispatcharr/issues/854)) — Thanks [@​CodeBormen](https://redirect.github.com/CodeBormen) - DVR enhancements — Thanks [@​CodeBormen](https://redirect.github.com/CodeBormen) - **Stop Recording**: A new Stop button (distinct from Cancel) cleanly ends an in-progress recording early and keeps the partial file available for playback. The API returns immediately; stream teardown and task revocation happen in a background thread to prevent 504 timeouts. When multiple recordings run simultaneously, stopping one only terminates that recording's proxy client by ID, leaving all others unaffected. (Closes [#​454](https://redirect.github.com/Dispatcharr/Dispatcharr/issues/454)) - **Extend Recording**: In-progress recordings can be extended by 15, 30, or 60 minutes without interrupting the stream. - **Inline metadata editing**: Title and description can now be edited directly in the recording details modal. - **Refresh artwork button**: Manually re-run poster resolution on demand from the recording card. - **Multi-source poster resolution**: Added pipeline querying EPG, VOD, TMDB, OMDb, TVMaze, and iTunes for richer recording artwork. - **Series rules for currently-airing episodes**: Series rules now capture currently-airing episodes in addition to future scheduled ones. (Closes [#​473](https://redirect.github.com/Dispatcharr/Dispatcharr/issues/473)) - **Search and filter controls**: Added search and filter controls to the recordings list. - **Stream generator throttling**: Cached the `ProxyServer` singleton reference per client and throttled Redis resource checks (1 s) and non-owner health checks (2 s), eliminating 3+ Redis round-trips per stream loop iteration. - **Automatic crash recovery on worker restart**: A `worker_ready` Celery signal now fires `recover_recordings_on_startup` automatically when the worker starts, so recordings stuck in "recording" status are recovered without manual intervention. - Account expiration tracking and notifications for M3U profiles - A new `exp_date` field on `M3UAccountProfile` stores the account expiration date as a proper `DateTimeField`. For Xtream Codes accounts the field is auto-synced from `custom_properties.user_info.exp_date` on every save (supports both Unix timestamps and ISO date strings). For non-XC M3U accounts the date can be entered manually via the account or profile form. - The M3U accounts table now shows an **Expiration** column displaying the earliest expiration date across all profiles for that account (color-coded: red = expired, orange = expiring soon, green = OK). Hovering the cell shows a tooltip with per-profile expiration details including inactive-profile labels. - A daily Celery Beat task (`check_xc_account_expirations`) checks all active profiles with an expiration date and manages system notifications: a normal-priority warning is raised for profiles expiring within 7 days; a high-priority alert is raised once the profile has already expired. Warning and expired notifications use separate keys so dismissing the 7-day warning does not suppress the expiration alert. - Notifications are also updated immediately when a profile is saved: if the expiration date is cleared or pushed beyond the 7-day window, any existing warning/expired notifications are deleted; if the date falls within the window or is already past, the matching notification is updated in place. - Non-XC accounts expose a `DateTimePicker` on both the M3U account form and the profile form. ##### Changed - Dependency updates: - `Django` 5.2.11 → 6.0.3 (security patch + major version upgrade; see Security section) - `django-celery-beat` ≥2.8.1 → ≥2.9.0 (adds explicit Django 6.0 support) - When selecting an EPG source for a channel, the EPG source dropdown now only lists enabled (active) EPGs, sorted alphabetically. - Channels page default splitter ratio changed from 50/50 to 60/40 (channels/streams) so all channel action buttons are visible without scrolling on 1080p displays. - Frontend component refactoring and cleanup — Thanks [@​nick4810](https://redirect.github.com/nick4810) - `FloatingVideo`, `SeriesModal`, `VODModal`, `SystemEvents`, `M3URefreshNotification`, and `NotificationCenter` significantly reduced in size by separating business logic into dedicated utility modules under `utils/components/` (`FloatingVideoUtils.js`, `SeriesModalUtils.js`, `VODModalUtils.js`, `NotificationCenterUtils.js`). - `FloatingVideo` resize handle elements extracted into a standalone `ResizeHandles` sub-component. - `YouTubeTrailerModal` extracted into a standalone component (`components/modals/YouTubeTrailerModal.jsx`). - `NotificationCenter` and `Sidebar` updated from Mantine dot-notation sub-components (`Popover.Target`, `Popover.Dropdown`, `ScrollArea.Autosize`, `AppShell.Navbar`) to Mantine v7 named imports (`PopoverTarget`, `PopoverDropdown`, `ScrollAreaAutosize`, `AppShellNavbar`). - `M3URefreshNotification` now uses the centralized `showNotification()` utility (from `notificationUtils.js`) instead of calling `notifications.show()` directly, bringing it in line with the rest of the app. State updates also converted to functional updater form (`prev => ...`) to eliminate potential stale-closure bugs. - `SystemEvents` now imports `format` from `dateTimeUtils` for consistent date/time formatting. - Removed a dead `onLogout` handler in `Sidebar` that called `logout()` and `window.location.reload()` but was never wired to any UI element. - EPG output when no `days` parameter is specified now excludes already-ended programs instead of returning all historical data. ##### Fixed - Single-stream channel creation modal not opening correctly when clicking the channel-creation button on an individual stream row in the Streams table. — Thanks [@​JCBird1012](https://redirect.github.com/JCBird1012) - DVR series rule creation failing with a 500 error when the stored `series_rules` data contained corrupted (non-dict) entries. Added type guards on the getter, setter, and generic settings serializer to filter invalid entries on read and write. Hardened the EPG ignore list getters (`prefixes`, `suffixes`, `custom`) with the same pattern. Frontend settings parse and save now validate `series_rules` with `Array.isArray()`, matching the existing EPG field pattern, preventing corrupted data from being round-tripped back to the database. (Fixes [#​1059](https://redirect.github.com/Dispatcharr/Dispatcharr/issues/1059)) — Thanks [@​CodeBormen](https://redirect.github.com/CodeBormen) - TS proxy clients stuck indefinitely in keepalive mode when a stream fails and never recovers (Fixes [#​1102](https://redirect.github.com/Dispatcharr/Dispatcharr/issues/1102), [#​1103](https://redirect.github.com/Dispatcharr/Dispatcharr/issues/1103)) — Thanks [@​cmcpherson274](https://redirect.github.com/cmcpherson274) & [@​CodeBormen](https://redirect.github.com/CodeBormen) - **Keepalive duration cap**: Non-owner worker clients sending keepalive packets to hold a connection open during failover can now be held at most `MAX_KEEPALIVE_DURATION` seconds (default 300 s). If no real stream data has been received within that window, the client is disconnected with a warning log. The timer resets each time real data resumes, so independent stalls do not accumulate. - **`last_active` tracking**: `last_active` is now updated on every keepalive packet and on every real data chunk, so clients actively waiting during a failover are not incorrectly evicted as ghost clients by the heartbeat thread. The heartbeat thread now only refreshes the Redis TTL rather than updating `last_active`, ensuring the ghost-detection check reflects true client activity rather than heartbeat activity. - **Buffer reset on stream transition**: A new `reset_buffer_position()` method on `StreamBuffer` clears the in-memory write buffer and partial-packet accumulator when switching between FFmpeg processes. Without this, a partial 188-byte TS packet from the dying FFmpeg process was being prepended to the first bytes from the new FFmpeg process, producing a corrupted TS packet boundary that broke audio decoder sync on the client side. Redis-stored chunks already consumed by clients are unaffected. - **`add_chunk()` locking hardened**: The lock scope in `add_chunk()` was expanded to cover the entire partial-packet merge and write-buffer accumulation phase, preventing a race condition between `add_chunk()` and the new `reset_buffer_position()` call. - uWSGI segfaults caused by mixing threading and gevent concurrency models. The dev and debug uWSGI configs had `threads` and `enable-threads = true` set alongside gevent, which triggers segmentation faults particularly on ARM64/Python 3.13. Removed those options to match the already-correct production config. — Thanks [@​jcasimir](https://redirect.github.com/jcasimir) - `Stream.last_seen` and `ChannelGroupM3UAccount.last_seen` model defaults now use `django.utils.timezone.now` instead of `datetime.datetime.now`, eliminating spurious `RuntimeWarning: DateTimeField received a naive datetime` warnings emitted during test database creation and on new record creation when `USE_TZ=True`. - EPG programme parsing crash when an XMLTV source contains programme titles exceeding 255 characters. Previously, a single oversized title would cause the entire `bulk_create` batch to fail with a database truncation error, silently dropping all programmes in that batch. Titles are now truncated to 255 characters before being saved. (Fixes [#​1039](https://redirect.github.com/Dispatcharr/Dispatcharr/issues/1039)) - Container startup failure when `PUID`/`PGID` is set, caused by `/data/db` ownership conflicts between the `postgres` system user (UID 102) and the configured PUID/PGID. PostgreSQL now runs as the PUID/PGID user in AIO mode, eliminating all `chown`-to-UID-102 operations and unifying `/data` ownership. (Fixes [#​1078](https://redirect.github.com/Dispatcharr/Dispatcharr/issues/1078)) — Thanks [@​CodeBormen](https://redirect.github.com/CodeBormen) - Existing installations where PUID/PGID differs from the current `/data/db` owner are migrated automatically on first startup; a sentinel file prevents redundant recursive `chown` on subsequent boots. - PUID/PGID auto-detected from existing data ownership when not explicitly set, avoiding cross-UID `chown` failures on restricted filesystems (NFS `root_squash`, CIFS). - PUID/PGID validated as positive non-zero integers before any user/group operations. - UID collisions with the `postgres` system user (e.g. PUID=102) are now handled gracefully. - Ensured proper variable quoting in the /docker/ directory to guard from inappropriate input - Floating video player bug fixes - **Resize stuck after releasing mouse outside window**: The `mouseup` event is not delivered when the pointer leaves the viewport, leaving the `mousemove` listener active indefinitely. Fixed by checking `event.buttons === 0` at the top of `handleResizeMove`; when no button is held the resize session is torn down immediately. - **Drag stuck after releasing mouse outside window**: Same root cause as the resize bug. Fixed by detecting `event.buttons === 0` in the `onDrag` handler and dispatching a synthetic `mouseup` event so react-draggable cleanly ends the drag session. - **Player draggable off screen**: The player could be dragged off any edge, making the header (and drag handle) unreachable. The player is now fully bounded: left and top edges are clamped to `x ≥ 0` / `y ≥ 0` so the header is always reachable, and right/bottom edges are clamped to the viewport. Size and position are also re-clamped automatically when the browser window is resized, with proportional scale-down if the saved size exceeds the new viewport. - Double error notification when saving user preferences: `API.updateMe` was catching errors internally and displaying a notification before re-throwing, causing callers to display a second notification for the same failure. - Navigation preference saves from concurrent sessions could overwrite each other due to a double-merge race: the frontend was pre-merging `custom_properties` before sending, then the backend merged again against the DB value, causing the second session's write to silently drop keys set by the first. The frontend now sends only the delta; the backend merges authoritatively against the stored value. - Stale nav item IDs (e.g. from a previous nav structure) are now scrubbed from `navOrder` and `hiddenNav` on the next preference save, preventing unbounded growth of the `custom_properties` JSON field. - Version update notification persisting after upgrading to the notified version (e.g. "v0.20.2 available" shown while already running v0.20.2). Root cause: `check_for_version_update.delay()` was called from `AppConfig.ready()`, which fires inside Celery prefork pool subprocesses before the broker connection is established, causing the dispatch to fail silently with no log output. Fixed by moving the startup dispatch to the `worker_ready` signal in `celery.py` (consistent with the existing `recover_recordings_on_startup` pattern), and deleting the stale `version-{current_version}` notification at the top of the production check path so it is cleared even when GitHub is unreachable. A WebSocket update is sent immediately on deletion so the frontend badge clears without waiting for the API response. - VOD orphan cleanup crashing with a `ForeignKeyViolation` (`IntegrityError`) when a concurrent refresh task created a new `M3UMovieRelation` or `M3USeriesRelation` for a movie/series between the orphan-detection query and the `DELETE` SQL. Both `orphaned_movies.delete()` and `orphaned_series.delete()` are now wrapped in `try/except IntegrityError`; affected records are skipped with a warning and will be cleaned up on the next scheduled run. - XC stream refresh crashing with a `null value in column "name"` database error when a provider returns streams with a null or empty name. Affected streams are now assigned a generated fallback name in the format ` - ` so the refresh completes successfully and the stream remains accessible. A warning is logged for each affected stream. - 504 Gateway Timeout when saving M3U group settings on slower hardware (e.g. Synology NAS). Replaced per-row `update_or_create()` loops with `bulk_create(update_conflicts=True)` wrapped in `transaction.atomic()` for both `ChannelGroupM3UAccount` and `M3UVODCategoryRelation`, reducing hundreds of individual DB round-trips to a single query per model. (Fixes [#​745](https://redirect.github.com/Dispatcharr/Dispatcharr/issues/745)) — Thanks [@​nickgerrer](https://redirect.github.com/nickgerrer) - Improved frontend table stability during M3U imports: Fixed incorrect default `state` initialization (`[]` → `{}`) in `CustomTable` to match TanStack Table v8's expected state object shape. Added `autoResetPageIndex: false` and `autoResetExpanded: false` to prevent TanStack Table from issuing internal state resets on data updates. Memoized `processedData` in `M3UsTable` to avoid redundant sort/filter recomputation on re-renders. - Thanks [@​marcinolek](https://redirect.github.com/marcinolek) - `debian_install.sh` hardened for non-UTF8 environments (common in minimal LXC containers) - Thanks [@​marcinolek](https://redirect.github.com/marcinolek) - Added `setup_locales` step that installs the `locales` package, enables `en_US.UTF-8`, regenerates locales, and exports `LANG`/`LC_ALL` before any other work runs, preventing PostgreSQL from defaulting to `SQL_ASCII` encoding. - PostgreSQL database creation now explicitly passes `-E UTF8` to `createdb`. - `PATH` in the Celery worker, Celery Beat, and Daphne systemd service files extended to include `/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin`, fixing failures where background tasks could not locate `ffmpeg` or `ffprobe`. - `is_adult` field parsing now guards against invalid values (e.g. the string `"None"`) that providers may send instead of a valid integer, preventing a `ValueError` crash during M3U/XC stream refresh. A new `parse_is_adult()` helper wraps the cast in a `try/except`, returning `False` for anything that cannot be interpreted as `1`. (Fixes [#​1061](https://redirect.github.com/Dispatcharr/Dispatcharr/issues/1061)) — Thanks [@​JCBird1012](https://redirect.github.com/JCBird1012) - M3U EXTINF attribute parsing for values containing `=` or `==` (e.g. base64-padded `tvg-logo` URLs, catchup tokens with query strings). The previous regex used `[^\s]+` for the key pattern, allowing `=` signs inside a quoted value to be greedily absorbed into the next attribute's key name, causing that attribute and all subsequent ones on the line to be silently dropped. Changed to `[^\s=]+` so the key match always stops at the first `=`. (Fixes [#​1055](https://redirect.github.com/Dispatcharr/Dispatcharr/issues/1055)) - Thanks [@​JCBird1012](https://redirect.github.com/JCBird1012) - Celery worker memory leak during M3U/XC refresh causing 20–80 MB growth per cycle with no reclamation (Fixes [#​1012](https://redirect.github.com/Dispatcharr/Dispatcharr/issues/1012), [#​1053](https://redirect.github.com/Dispatcharr/Dispatcharr/issues/1053)) - Thanks [@​CodeBormen](https://redirect.github.com/CodeBormen) - Restructured `refresh_single_m3u_account()` with a `try/finally` that guarantees `del` of large data structures runs before Celery's `gc.collect()`, and lock release on all exit paths (success, exception, early return) - Re-enabled batch data cleanup in `process_m3u_batch_direct()` (was commented out) - Added `CELERY_WORKER_MAX_MEMORY_PER_CHILD = 512 MB` as a safety net against pymalloc arena fragmentation - EPG output was filtering programs using `start_time__gte=now` when the `days` parameter was specified, which caused currently-airing programs (started before the request time but not yet ended) to be omitted from the XML output. This produced a gap in clients' guides immediately after an EPG refresh, lasting until the next program started. Fixed by changing the filter to `end_time__gte=now` so any program that has not yet finished is included. - TS proxy connection slot leaks and TOCTOU races in stream initialization (Fixes [#​947](https://redirect.github.com/Dispatcharr/Dispatcharr/issues/947)) - Thanks [@​CodeBormen](https://redirect.github.com/CodeBormen) - **TOCTOU race in slot reservation**: `get_stream()` previously used a `GET`→check→`INCR` sequence, allowing concurrent requests to both read the same count below the limit and both reserve a slot, silently exceeding `max_streams`. Replaced with an atomic `INCR`-first pattern: increment unconditionally, check the result, roll back with `DECR` if over capacity. — Thanks [@​patchy8736](https://redirect.github.com/patchy8736) - **Leak on URL generation failure**: `generate_stream_url()` called `get_stream()` (which `INCR`s the counter) but had no cleanup path if subsequent DB lookups or URL construction failed. The post-`get_stream()` block is now wrapped in a `try/except` that calls `release_stream()` on any error. - **Leak on retry-loop timeout**: the retry loop in `stream_ts()` called a bare `get_stream()` on the first failure to classify the error reason. If a slot was available, this `INCR`'d the counter and set Redis keys that were never released when the loop timed out. A `release_stream()` call is now issued before returning 503. - **Leak on `initialize_channel()` failure**: when `initialize_channel()` returned `False`, the connection slot allocated by the preceding `get_stream()` was never released. A `connection_allocated` flag now tracks whether this request performed the `INCR` (fresh initialization vs. joining an existing channel), and `release_stream()` is called guarded by that flag to prevent incorrect decrements when attaching to an already-running channel. - **Safety net for unexpected exceptions**: the outer `except` in `stream_ts()` now checks `connection_allocated` and calls `release_stream()` as a last-resort cleanup for any unhandled exception that escapes before the channel is handed off to the stream lifecycle. - **`release_stream()` now returns `bool`** and adds a metadata-hash fallback: if the primary `channel_stream` / `stream_profile` Redis keys have already been cleaned up by the proxy, it recovers `stream_id` and `profile_id` from the channel's metadata hash and clears those fields atomically to prevent duplicate `DECR`s on repeated calls. — Thanks [@​patchy8736](https://redirect.github.com/patchy8736) - **`update_stream_profile()` uses a Redis pipeline** for the old-profile DECR + key update + new-profile INCR sequence, preventing counter drift if the process crashes between operations. - **`stream_generator._cleanup()`** now falls back to `Stream.objects.get()` when the channel UUID resolves to a preview flow rather than a normal channel, rather than silently skipping the slot release. - **VOD `cleanup_persistent_connection()`** fallback DECR is now conditional: it only decrements the profile counter when the connection tracking key had already expired by TTL (i.e., `remove_connection()` would have skipped the DECR), preventing double-decrements when the key is still present. - Ghost clients and channels stuck in `INITIALIZING` state in the TS proxy (Fixes [#​695](https://redirect.github.com/Dispatcharr/Dispatcharr/issues/695), [#​669](https://redirect.github.com/Dispatcharr/Dispatcharr/issues/669)) — Thanks [@​CodeBormen](https://redirect.github.com/CodeBormen) - **`INITIALIZING` added to cleanup grace period monitoring**: channels stuck in `INITIALIZING` are now surfaced by the cleanup task and torn down, preventing indefinite hangs when stream startup fails. - **Orphaned channel cleanup validates client SET entries**: the cleanup task now cross-checks client SET members against actual metadata hashes; ghost SET entries are removed and the channel is torn down cleanly when no real clients remain. - **Stats page self-heals**: ghost client SET entries are detected and removed when reading channel stats, preventing stale entries from inflating the active-client count. - **`remove_ghost_clients()` extracted to `ClientManager`**: ghost-detection logic is now a single authoritative helper, callable with an optional pre-fetched `client_ids` set to eliminate a redundant Redis `SMEMBERS` round-trip when the caller already holds the set. - **Ownership TTL fallback**: the error-state writer now triggers via ownership check *or* state guard fallback, so a channel stuck in a pre-active state is correctly marked `ERROR` even when the ownership TTL expired during retries. - **Missing `SOURCE_BITRATE` / `FFMPEG_BITRATE` metadata constants** added to `ChannelMetadataField`, preventing `AttributeError` on detailed channel stats reads. - TS proxy client stream lag recovery now only bumps clients forward when their next required chunk has genuinely expired from Redis (TTL), rather than unconditionally jumping if they fell more than 50 chunks behind. Clients are repositioned to the oldest available chunk (minimum data loss) using an atomic server-side Lua binary search, falling back to near the buffer head if nothing is available. - TS proxy streams dying after 30–200 seconds in multi-worker uWSGI/Celery deployments, caused by three interrelated bugs. (Fixes [#​992](https://redirect.github.com/Dispatcharr/Dispatcharr/issues/992), [#​980](https://redirect.github.com/Dispatcharr/Dispatcharr/issues/980)) - Thanks [@​PFalko](https://redirect.github.com/PFalko) - **Double ProxyServer instantiation**: `ProxyConfig.ready()` called `TSProxyServer()` directly while `TSProxyConfig.ready()` also called `TSProxyServer.get_instance()`, creating two instances per worker — each with its own cleanup thread. The orphaned thread could not extend ownership because it had no entries in `stream_managers`. Fixed by using `TSProxyServer.get_instance()` in `ProxyConfig.ready()`. - **`flushdb()` on every Redis client init**: `RedisClient.get_client()` called `client.flushdb()` whenever `_client` was `None`. Celery autoscale (`--autoscale=6,1`) spawning new workers mid-stream triggered this path, nuking all Redis keys including active ownership keys, client records, and channel metadata. Removed the `flushdb()` call entirely. - **No recovery from expired ownership**: `get_channel_owner()` called `redis.get()` twice inside a lambda (TOCTOU race — key could expire between calls); `extend_ownership()` silently returned `False` on expiry with no re-acquisition; and the non-owner cleanup path unconditionally killed streams even when the worker held the `stream_manager`. Fixed with a single `GET` in `get_channel_owner()`, re-acquisition via atomic `SET NX EX` in `extend_ownership()`, and a re-acquisition attempt with client-aware cleanup deferral in the cleanup thread. - `get_instance()` deadlock: if `ProxyServer()` raised an exception during singleton construction, `_instance` was left permanently as the `_INITIALIZING` sentinel, causing all subsequent `get_instance()` callers to spin in an infinite `gevent.sleep()` loop. Construction is now wrapped in `try/except`; on failure `_instance` resets to `None` so the next call can retry. - Non-atomic ownership acquisition in `try_acquire_ownership()`: replaced the separate `setnx()` + `expire()` calls with a single atomic `SET NX EX`, eliminating the race window where a process crash between the two calls could leave an ownership key with no TTL (permanent ownership lock). - DVR bug fixes — Thanks [@​CodeBormen](https://redirect.github.com/CodeBormen) - **Duplicate recording execution**: `run_recording.apply_async(countdown=...)` exceeded Redis' default `visibility_timeout` (3600 s) for recordings scheduled more than one hour out, causing Redis to redeliver the task to multiple workers simultaneously and producing corrupted output files. Replaced `apply_async` with `ClockedSchedule` + `PeriodicTask` for database-backed one-shot scheduling that survives restarts and upgrades without the redelivery race. `run_recording` also now exits immediately if the recording is already in progress, completed, or stopped. `revoke_task()` cleans up both the `PeriodicTask` and its orphaned `ClockedSchedule` on execution. (Fixes [#​940](https://redirect.github.com/Dispatcharr/Dispatcharr/issues/940), [#​641](https://redirect.github.com/Dispatcharr/Dispatcharr/issues/641)) - **Stream reconnection resilience**: Recordings now survive transient network drops with automatic reconnection retrying up to 5 times and appending to the existing file. DB operations use exponential-backoff retry for transient database errors throughout the recording lifecycle. - **Crash recovery pipeline**: On worker restart, recordings stuck in "recording" status have their segments concatenated and remuxed. Remux sanity checks reject MKV output that is less than 50% the size of a previous MKV (duplicate-task overwrite) or less than 10% of the source TS (corrupt first attempt); the source `.ts` is preserved for manual recovery on all failure paths. (Fixes [#​619](https://redirect.github.com/Dispatcharr/Dispatcharr/issues/619), [#​624](https://redirect.github.com/Dispatcharr/Dispatcharr/issues/624)) - **Output file collision**: Fixed collision when multiple tasks targeted the same filename. - **WebSocket deadlock**: `send_websocket_update()` was deadlocking the gevent event loop, causing one recording's WebSocket events to block all other simultaneous recordings. - **DVR client isolation**: Stop and Cancel operations now identify the target client by recording ID (via `User-Agent: Dispatcharr-DVR/recording-{id}`), ensuring only the correct proxy client is torn down and never affecting other recordings on the same channel. - **Accidental stream termination on delete**: `destroy()` now only calls `_stop_dvr_clients()` for in-progress recordings, preventing stream termination when deleting a completed recording. - **Recording card logos**: Logos were not displaying due to a channel summary API shape mismatch. - **Logo fetch negative cache**: Added negative cache for failed remote logo fetches so dead CDNs no longer block Daphne workers on repeated requests. - **Artwork fuzzy-match sanitisation**: Poster artwork fuzzy-matching against external APIs (TMDB, OMDb, etc.) was producing incorrect results for channels with names like "USA A\&E SD\*"; channel-name strings are now sanitised before querying external sources. - **Series modal "No upcoming episodes"**: Fixed due to a missing `_group_count` merge and an incorrect time filter. - **Series rule cleanup**: Deleting a series rule left orphaned recordings and stale Guide indicators; rule deletion now cleans up all associated recordings. Orphaned recordings with no parent rule are also cleaned up automatically. (Fixes [#​1041](https://redirect.github.com/Dispatcharr/Dispatcharr/issues/1041)) - **Series rule timezone calculation**: Recurring rules silently dropped scheduled recordings for users in UTC-negative timezones after 4 pm local time. (Fixes [#​1042](https://redirect.github.com/Dispatcharr/Dispatcharr/issues/1042)) - **Recording modal TDZ crash**: Modal crashed on load in production bundles due to a Temporal Dead Zone error — editing state was referenced before its declaration in the minified bundle. - **Description textarea focus loss**: The description textarea lost focus immediately when opened because the inline editing component was remounting on every render. - **WebSocket-driven refresh**: Replaced all manual `fetchRecordings()` polling calls with debounced WebSocket-driven refresh so the recordings list stays up to date without redundant API requests. - **comskip exit code handling**: comskip treated exit code 1 ("no commercials found") as a fatal error, causing post-processing to fail on clean recordings. Exit code 1 is now recognised as a successful no-op. - **Differentiated WebSocket notification events**: `recording_stopped`, `recording_cancelled` (in-progress cancel), and `recording_deleted` with a `was_in_progress` flag now allow the frontend to display distinct "Recording stopped", "Recording cancelled", and "Recording deleted" toasts. - **Duplicate series rule evaluation race**: Creating a series rule fired `evaluate_series_rules.delay()` in the API view while the frontend immediately called the synchronous evaluate endpoint, racing to create duplicate recordings for the same program. Removed the redundant async call from the API; the frontend's explicit evaluate call is now the sole evaluation path. - **Recording card S/E badge overlap**: Season/episode badges were overlapping and metadata was hidden on the recording card. - **Orphaned recording fallback in series modal**: When a series rule no longer exists, the recurring rule modal now shows a "Delete Recording" button for the orphaned recording instead of failing silently. - Modular mode deployment hardening — Thanks [@​CodeBormen](https://redirect.github.com/CodeBormen) - **Postgres version check with restricted DB users**: The version check was connecting to the hardcoded `postgres` database, which fails when the configured user lacks access to it. Changed to use `$POSTGRES_DB` so the check works with least-privilege database users. (Fixes [#​1045](https://redirect.github.com/Dispatcharr/Dispatcharr/issues/1045)) - **DVR recording broken in modular mode**: Internal TS stream URL candidates hardcoded port `9191`, so recordings failed when `DISPATCHARR_PORT` was set to any other value. URL construction now reads `DISPATCHARR_PORT` from the environment via the new `build_dvr_candidates()` helper. `DISPATCHARR_PORT` is also now explicitly passed to the Celery container in `docker-compose.yml`. - **Selective Redis flush in modular mode**: `wait_for_redis.py` now performs a targeted flush in modular mode — clearing stale stream locks, proxy metadata, and server-state keys — while preserving Celery broker and result-backend keys. Previously either a full `flushdb()` (which wiped Celery queues) or no flush at all was performed. - **Redis wait stripping environment variables**: The modular-mode Redis readiness check ran as a uWSGI `exec-pre` hook, which executes under `su -` and strips Docker environment variables, making `DISPATCHARR_ENV` and `REDIS_HOST` unavailable. Moved to the container entrypoint so all env vars are present. - **Stale environment variables after container restart**: `/etc/profile.d/dispatcharr.sh` was only written on the first container run; restarts with changed env vars (e.g. a rotated `POSTGRES_PASSWORD`) retained stale values. The file is now truncated and rewritten on every startup. `/etc/environment` entries are likewise updated rather than skipped when a key already exists. All exported values are now quoted to prevent breakage from special characters. - **Celery entrypoint startup timeouts**: The JWT key wait and migration wait loops had no timeout, leaving the Celery worker hanging indefinitely if the web container was stuck. Each loop now times out (120 s for JWT, 300 s for migrations) and exits with a clear diagnostic message. The migration readiness check is also replaced from a fragile `showmigrations | grep` pattern to `migrate --check`, which exits cleanly on both unapplied migrations and connection errors. - **Service startup ordering**: `depends_on` entries for `db` and `redis` in `docker-compose.yml` upgraded from plain name-link ordering to `condition: service_healthy`, ensuring containers wait for actual readiness signals before starting. - **`host.docker.internal` resolution on Linux**: Added `extra_hosts: host.docker.internal:host-gateway` to the web service in `docker-compose.yml` so Linux hosts resolve `host.docker.internal` the same way Docker Desktop does on macOS/Windows.
--- ### Configuration 📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined). 🚦 **Automerge**: Enabled. ♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox. 🔕 **Ignore**: Close this PR and you won't be reminded about this update again. --- - [ ] If you want to rebase/retry this PR, check this box --- This PR has been generated by [Renovate Bot](https://redirect.github.com/renovatebot/renovate). --- charts/stable/dispatcharr/Chart.yaml | 4 ++-- charts/stable/dispatcharr/values.yaml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/charts/stable/dispatcharr/Chart.yaml b/charts/stable/dispatcharr/Chart.yaml index aec0dfebdb1..0db180ac810 100644 --- a/charts/stable/dispatcharr/Chart.yaml +++ b/charts/stable/dispatcharr/Chart.yaml @@ -8,7 +8,7 @@ annotations: trueforge.org/min_helm_version: "3.14" trueforge.org/train: stable apiVersion: v2 -appVersion: 0.20.2 +appVersion: 0.21.1 dependencies: - name: common version: 28.33.4 @@ -36,5 +36,5 @@ sources: - https://github.com/trueforge-org/truecharts/tree/master/charts/stable/dispatcharr - https://hub.docker.com/r/dispatcharr/dispatcharr type: application -version: 1.7.3 +version: 1.8.0 diff --git a/charts/stable/dispatcharr/values.yaml b/charts/stable/dispatcharr/values.yaml index b9d267d3117..3b5a683630b 100644 --- a/charts/stable/dispatcharr/values.yaml +++ b/charts/stable/dispatcharr/values.yaml @@ -2,7 +2,7 @@ image: pullPolicy: IfNotPresent repository: docker.io/dispatcharr/dispatcharr - tag: 0.20.2@sha256:116f7f4ffa512afc4d196f2f1649bb5af55a3384097632c33203dd8feb37a91b + tag: 0.21.1@sha256:3fe4da0af06887781aba4650bb64bbffc3a10f11a602d4d5a1a8df6a8edca416 securityContext: container: readOnlyRootFilesystem: false