v2026.07.07 - #51
Merged
Merged
Conversation
…ependabot config - cut-release.yml: manual workflow that tags main (CalVer, collision-checked), dispatches the release build (token-pushed tags don't fire on:push), and merges main back into develop automatically - release.yml: accepts workflow_dispatch on tag refs and comments 'Fixed in vX' on every issue closed by the release's commits - dependabot.yml: weekly grouped version-update PRs targeting develop - docs/RELEASING.md: rewritten release process notes
The failsafe shutdown path split the configured shutdown total across clients and applied it directly via set_speed_limits(), with no floor. A shutdown speed of 0 (or a 0% client share) therefore sent 0 to clients, which qBittorrent, NZBGet and Deluge all read as 'unlimited' -- the opposite of throttling. This is the same 0=unlimited class fixed for the live path in #43, which scoped out the shutdown path. - Move HARD_MIN_MBPS to constants.py as the shared floor (decision_engine now imports it from there; live-throttle behavior unchanged). - Floor every per-client value in _split_shutdown_speed to HARD_MIN_MBPS so a shutdown of 0 or a 0% share becomes a non-zero trickle, never unlimited. - Add ge=0 validation to FailsafeConfig shutdown_download_speed/upload_speed. - Clamp the frontend shutdown-speed inputs against negatives/NaN and note that 0 means a minimum trickle. - Add tests covering zero-total, zero-share, never-sends-0, and validation.
Replace global include_lan_streams flag with per-stream policy so each Plex server can independently control whether its LAN streams count toward bandwidth calculations. Missing policy defaults to exclude LAN.
Adds aggregate_per_server_bandwidth() helper that sums stream bitrate per server_id, and populates BandwidthMetric.per_server with the JSON-serialised result on each polling cycle.
Per-client bandwidth allocations were keyed by client type (or
client_id.split('_')[0]), so two clients of the same type (e.g. two
qBittorrent) collided on one key and were forced to an equal split.
Re-key the live decision-engine download and upload split branches and
the failsafe shutdown-speed split to look up percentages by the unique
client id directly. Remove the now-dead get_client_type /
get_normalized_percents / get_raw_upload_percent helpers.
Adds engine tests for two same-type clients splitting 60/40 (download)
and 70/30 (upload), a legacy id==type regression test, and a same-type
failsafe split test; re-keys the stale type-keyed shutdown fixture.
Re-key the stored per-client percent dicts (live + scheduled download/ upload and failsafe shutdown) from client type to unique client id: - resolve_percent_key / normalize_percent_dict / normalize_client_percent_keys: pure in-memory normalizer applied on every config load (read-time safety net, no DB I/O). Rule order: id-exact keeps, single-type renames to that id, multi-type collision drops, dead key drops. - migrate_client_percent_keys: one-time persisted migration run once at startup that deletes superseded type-keyed rows and inserts renamed id-keyed rows (idempotent). Persistence lives only at startup, never in the read-only load path. - cleanup_bandwidth_percent_keys: clears live+scheduled percent rows before a bandwidth save so renamed/removed id keys can't be resurrected. - settings _prune_percent_dicts: prune all six percent dicts to the live client-id set on client add/remove (replaces the old type-set reset). - config.py field docs updated to say client id; no schema change. Two user-visible behaviors on first upgrade: - A config that previously had two colliding same-type clients resets to an equal split until a slider is moved. - Per-client splits are pruned to the current client-id set on any client save. Adds pure-helper, DB migration (rename/drop/dead-key/idempotent), cleanup, and prune tests.
Re-key every per-client percent getter, updater, and binding in the bandwidth settings, setup wizard, and failsafe settings to use the unique client.id instead of client.type. This fixes the unmovable split slider for two clients of the same type: the slider updater's find(c => c.type !== clientType) returned undefined for a same-type pair and bailed before writing; it now uses find(c => c.id !== clientId). The dashboard chart is intentionally left keyed by type (its data series are aggregated per type by the chart-data API), so same-type clients merge into one series; a comment documents this and per-client-id chart series is left as a follow-up.
Record bandwidth per client id in a new per_client JSON column and serve per-id chart fields plus a client_series descriptor list, so the chart can plot multiple same-type clients separately. Legacy per-type columns now hold the sum across same-type clients (fixing a first-client-only undercount), and pre-fix rows fall back to one merged series per type.
Drive every per-client chart element (gradients, areas, limit lines, aggregation, stacking, legend, visibility) from an id-keyed series descriptor list built from the status clients and chart-data client_series, with an auto-distinct color fallback for same-type clients. Fixes two qBittorrent clients merging into one mislabeled series; pre-fix history shows a merged line that splits into per-id lines for new data.
The chart-data API returns both legacy type-keyed series (from pre-fix rows) and the new per-id series, which rendered as duplicate legend entries (e.g. two "qBittorrent Download"). Fold each legacy type series into the first current client of that type so single-instance clients show one continuous line across the storage cutover, and drop the now-redundant legacy entry from the legend. Removed individual clients and fully-removed types are preserved.
Add the per_client JSON column (mirroring BandwidthMetric), record decisions per client id in the polling writer, and serve them per id with a legacy-column fallback for pre-migration rows. Also removes the orphaned Plex failsafe globals (superseded by per-server _server_state).
… override Emby/Jellyfin /Sessions exposes no per-session local flag, so streams were classified by a blanket RFC1918 check that ignored each server's configured LAN subnets. Now Speedarr reads LocalNetworkSubnets from each Emby/Jellyfin config API and classifies against it, with a manual per-server lan_networks CIDR override for all server types; Plex keeps its per-session signals. Shared IP/CIDR logic moves to app/utils/network.py (is_private_ip, ip_in_networks, classify_lan), removing the duplicated _is_private_ip from the Plex and Jellyfin adapters. Auto-read subnets refresh at startup, on media-server settings save, and on a successful Test Connection — never in the 5s poll loop. Adds a per-server LAN networks override input in settings.
…rvers Jellyfin/Emby leaked the embedded video track Title (the movie/episode name) into the Quality column because quality_profile was set from MediaStreams[0].DisplayTitle - the first stream is not guaranteed to be the video stream, and DisplayTitle embeds the track Title. Add a pure helper (app/utils/quality.py) that normalizes resolution tokens to a single canonical vocabulary (4K, 1080p, 720p, 480p, SD, 8K). Jellyfin/Emby now select the video stream by Type and extract the last resolution token from its DisplayTitle; Plex normalizes its videoResolution through the same helper. All three servers now emit a consistent, title-free Quality value.
…te-key collisions
…che aliasing The WAN gateway quantizes its SNMP octet counters to a ~5s internal cache, equal to the 5s poll interval. Two-point differencing of cumulative counters then aliased: each poll window randomly caught 0, 1, or 2 device refresh steps, producing 0.00 dropouts and 2x spikes (observed up to ~1900 Mbps) that slipped past the 10 Gbps sanity gate. Those spurious values also fed the decision engine and caused over-aggressive download throttling. Replace the last-two-reads delta with a rate averaged over a 30s sliding window (RATE_WINDOW_SECONDS). Counters are cumulative, so the windowed byte delta is exact; only the window endpoints carry the device's <=5s refresh phase error, which is negligible over 30s (~2% vs the previous +/-50%+). Adds compute_windowed_rate() helper and unit tests covering aliasing cancellation, counter wrap (32/64-bit), and insufficient-sample handling.
Poll Unraid's GraphQL API and throttle download clients while a parity check, the mover, or a degraded array is active. Replaces the bash-script integration with native monitoring. - UnraidConfig schema + settings section + test-connection endpoint - UnraidMonitor GraphQL client (parity / mover / degraded-array detection) - PollingMonitor override slot + dedicated poll loop; combine with manual temporary limits by most-restrictive so 0 still floors to a trickle - Hold last-known throttle when the Unraid API is unreachable - Rebuild/disable the monitor on settings save - Surface the override in GET /temporary-limits - unraid_throttle_started / unraid_throttle_ended notification events
- New Unraid panel (enable, URL, API key, condition toggles, shared download/upload limits, test connection) under the Integrations tab - Remove the parity-check / mover bash-script section from API Keys - Add the Unraid throttle start/end events to the notification selector
…pt approach (#30) The Unraid GraphQL API does not expose a live parity check on Unraid 7.3.1 / API 4.34.0: array.parityCheckStatus returns the last COMPLETED check, vars.mdResync* are cached/stale, mdResyncAction never clears on stop, and arraySubscription is broken (returns null). The live parity signal only exists host-side (/proc/mdstat), so the original script-based push integration is restored. Reverts commits 1453ce3 and 42ffb16.
…e 3.5.0 Clears 44 Dependabot alerts (33 aiohttp, 9 python-multipart, 2 python-jose including the critical CVE-2024-33663). No code changes required: aiohttp is client-only here, python-multipart is an unused FastAPI soft dependency (login is JSON), and JWT decode already pins algorithms=[HS256]. python-jose 3.5.0 (not 3.4.0) because 3.4.0 pins pyasn1<0.5.0.
Replaces the deprecated pysnmp-lextudio package (which pinned pyasn1<0.6.1 and blocked the fix for the pyasn1 unbounded-recursion DoS, CVE-2026-30922) with the maintained pysnmp 7.1.x line. Code changes required by 7.x: snake_case get_cmd/next_cmd imports, awaited UdpTransportTarget.create() at all four call sites, engine.close_dispatcher(), and a flattened _walk_oid loop since next_cmd now returns a flat tuple of ObjectType instead of 6.x's list-of-rows. Adds walk regression tests pinning the flat shape and drops the now-dead pysnmp-lextudio warning filter. Supersedes PR #35 (which proposed pysnmp 6.2.6, an unmaintained migration-helper release, and kept the vulnerable pyasn1 pin). Credit to @NeoMod for flagging the deprecated dependency.
…ubtree _walk_oid's boundary check compared raw OID strings, so walking ...31.1.1.1.1 (ifName) would not stop at sibling columns like ...31.1.1.1.15 (ifHighSpeed) whose number begins with the same digit. On devices where the walk lands on such a column, the extra rows key by the same last OID component as real interfaces and overwrite their names in discovery. Compare with a trailing dot so only true sub-OIDs match.
vite 5.x is EOL and received no backports for its three open advisories (dev-server-only exposure, but unfixable on the 5.x line). 6.4.3 is a drop-in for this config: plugin-react ^4.2.1 already resolves to a vite-6-compatible release and the node:20.11 build stage satisfies vite 6's engine range. Also commits package-lock.json and switches the Docker build to npm ci — image builds previously ran a bare npm install with no lockfile, silently floating every frontend dependency to the newest in-range version on each build.
The previous lockfile was generated with npm install --package-lock-only, which omits platform-specific optionalDependencies (npm/cli#4828). npm ci then installed rollup without its native binary and vite build failed on CI's linux/amd64 leg with 'Cannot find module @rollup/rollup-linux-x64-musl'. Regenerated via a full npm install so the lockfile records every platform variant; verified with a linux/amd64 build of the frontend stage.
…nfig Covers the previously-unwatched base images in backend/Dockerfile, holds new releases for 7 days (30 for majors) before proposing them, and documents that Dependabot only reads this file from the default branch - the config is inert until it reaches main with the next release.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Turns Speedarr into a multi media-server platform and clears all 50 open Dependabot alerts.
87 commits, 115 files.
Changes
status, charts, and settings (relates to [Feature Request] Add support for multiple media servers #47, Add support for Emby as media server #32)
clients of the same type no longer collide
and failsafe paths (relates to When plex consumption = upload bandwidth qbittorrent is then unlimited #43, Failsafe shutdown speed of 0 (or a 0% client share) sets clients to unlimited instead of throttling #48)
dedupe/quality normalization, settings 500s
thanks @NeoMod), vite 6 + locked frontend deps; resolves all 50 Dependabot alerts on merge
Upgrade notes