Files
sofarr/CHANGELOG.md
Gronod 3e06bdf8cd
All checks were successful
Build and Push Docker Image / build (push) Successful in 28s
Create Release / release (push) Successful in 6s
Docs Check / Markdown lint (push) Successful in 56s
Licence Check / Licence compatibility and copyright header verification (push) Successful in 46s
Docs Check / Mermaid diagram parse check (push) Successful in 1m31s
CI / Security audit (push) Successful in 1m50s
CI / Tests & coverage (push) Successful in 1m55s
Update CHANGELOG.md with 1.5.2 and 1.5.3; update README.md version reference
2026-05-19 23:11:47 +01:00

15 KiB
Raw Permalink Blame History

Changelog

All notable changes to this project will be documented in this file. Format follows Keep a Changelog. This project adheres to Semantic Versioning.


[1.5.3] - 2026-05-19

Fixed

  • Status panel rendering regression — the status panel was rendering as a small blank box due to an undefined --background CSS variable. Added the missing variable to all three themes (light, dark, mono).
  • Status panel content destructionshowDashboard() was calling sp.innerHTML = '' which destroyed the status-content div inside the status panel. Removed this destructive line.
  • Webhooks panel visibility sync — the webhooks panel was incorrectly visible on app load. Added explicit hiding of webhooks-section in showDashboard() to keep it in sync with the status panel (both show/hide together via toggleStatusPanel()).
  • Webhooks panel DOM structure — reverted webhooks-section to be a sibling of status-panel (not nested inside it), preventing innerHTML operations from affecting webhook elements.

[1.5.2] - 2026-05-19

Fixed

  • Status panel close button CSP compliance — replaced inline onclick handler with addEventListener to comply with CSP nonce policy.

[1.5.1] - 2026-05-19

Fixed

  • Webhook endpoints not reachable in productionserver/index.js (the production entry point) was missing the webhookRoutes import and mount. Only server/app.js (the test factory) had the routes registered. As a result every POST /api/webhook/* request in a running container fell through to the verifyCsrf middleware and was rejected with 403 CSRF token missing. Added app.use('/api/webhook', webhookRoutes) in index.js immediately after authRoutes and before verifyCsrf, matching the order in app.js.

[1.5.0a] - 2026-05-19

Fixed

  • Status panel close button — the × button now correctly hides the status panel and stops the auto-refresh timer. The button was previously using an inline onclick attribute which was silently blocked by the server's CSP nonce policy. Replaced with addEventListener wired after innerHTML is set, consistent with all other button handlers in the application.

[1.5.0] - 2026-05-19

Changed

  • ARCHITECTURE.md — consolidated the two existing architecture documents (root concise reference and docs/ARCHITECTURE.md deep-dive) into a single comprehensive reference at the project root. The merged document covers all 11 sections: Introduction, High-Level Architecture, Pluggable Architecture Layers (PDCA + PALDRA), Webhook System, Data Flow and Real-time Updates, Caching and Smart Polling, Key Subsystems, Directory Structure, Configuration and Environment Variables, Security Model, and Technology Stack. Includes full Mermaid diagrams for system overview, polling cycle, webhook path, UI state machine, and download matching pipeline.
  • docs/ARCHITECTURE.md — removed; content fully merged into root ARCHITECTURE.md.

[1.4.0] - 2026-05-19

Added

Webhook Integration (Phases 15.1)

  • Webhook receiver endpointsPOST /api/webhook/sonarr and POST /api/webhook/radarr accept push events from Sonarr and Radarr with shared-secret validation (X-Sofarr-Webhook-Secret header). Dashboard updates in < 1 second after any grab, import, or failure.
  • Selective cache invalidation — each incoming event is classified into queue events (Grab, Download, DownloadFailed, ManualInteractionRequired) or history events (DownloadFolderImported, ImportFailed, EpisodeFileRenamed, MovieFileRenamed). Only the affected cache key is refreshed via a lightweight re-poll of that instance, rather than a full poll cycle.
  • SSE broadcast on webhook — after refreshing the cache, pollAllServices() is called as a fire-and-forget, triggering an immediate SSE push to every connected browser.
  • Notification management API proxyGET /api/sonarr/api/v3/notification and POST /api/sonarr/api/v3/notification (and Radarr equivalents) proxy the full *arr Notification API through sofarr with auth + CSRF enforcement.
  • One-click webhook setupPOST /api/sonarr/webhook/enable and POST /api/radarr/webhook/enable auto-configure the Sofarr webhook notification connection inside the respective *arr service. POST /api/sonarr/webhook/test and /radarr/webhook/test trigger a test event.
  • Webhooks Configuration UI — collapsible panel in the dashboard allowing users to enable, test, and view webhook status for each configured Sonarr/Radarr instance, with per-trigger type indicators showing which event types are active.
  • SOFARR_WEBHOOK_SECRET environment variable — required for webhook endpoints to accept requests. Generate with openssl rand -hex 32.
  • SOFARR_BASE_URL environment variable — public URL of sofarr, used by the one-click setup to tell Sonarr/Radarr where to POST events.

Smart Polling Optimization (Phase 5)

  • Webhook metrics trackingcache.js now maintains per-instance and global webhook metrics (lastWebhookTimestamp, eventsReceived, pollsSkipped) via getWebhookMetrics(), updateWebhookMetrics(), incrementPollsSkipped(), getGlobalWebhookMetrics().
  • Conditional poll skippingpoller.js calls shouldSkipInstancePolling() before each Sonarr/Radarr fetch. If all instances of a type have received a webhook event within the fallback timeout window, their queue/history API calls are skipped entirely; existing cached data has its TTL extended instead.
  • Webhook fallback — if no webhook events have been received globally for WEBHOOK_FALLBACK_TIMEOUT minutes (default: 10), the poller forces a full poll regardless of per-instance state. Logged as [Poller] Webhook fallback triggered.
  • Poll-skip logging — logs [Poller] Skipping sonarr/radarr polling for N instance(s) with active webhooks when polling is skipped.
  • WEBHOOK_FALLBACK_TIMEOUT environment variable — minutes before fallback polling (default: 10).
  • WEBHOOK_POLL_INTERVAL_MULTIPLIER environment variable — internal multiplier for TTL calculations when webhooks are active (default: 3).
  • Phase 5.1 metrics connectionwebhook.js calls cache.updateWebhookMetrics(instance.url) after every successfully validated event, activating the smart skip logic for that instance.

Security Hardening (Phase 6)

  • Dedicated webhook rate limiter — 60 requests per minute per IP on /api/webhook/*, stricter than the global 300/15 min API limiter. Bypassed in tests via SKIP_RATE_LIMIT=1.
  • Strict input validationvalidatePayload() rejects: non-object bodies, missing/non-string/overlong eventType, unrecognised event type values (allowlist of 18 known *arr event types), non-string instanceName. Returns 400 with a descriptive message.
  • Replay protectionisReplay() tracks recently-seen (eventType, instanceName, date) tuples in a Map with a 5-minute TTL. Duplicate events within the window return 200 { received: true, duplicate: true } without triggering a cache refresh or SSE broadcast.
  • 35 new webhook integration tests — cover secret validation (missing/wrong/unconfigured), payload validation (all invalid cases), replay protection, happy-path acceptance of all relevant event types, metrics increment, and secret-never-leaks assertions.

Documentation (Phase 6)

  • README.md — updated architecture overview diagram, added Webhooks section with quick-setup guide, added PDCA/PALDRA/webhook architecture table, added Webhooks & Smart Polling env var section, added webhook API endpoints, updated test count.
  • CHANGELOG.md — this entry.
  • SECURITY.md — added webhook threat model rows, webhook-specific hardening checklist, and rate-limit table entry for webhook endpoints.
  • ARCHITECTURE.md (root) — new concise top-level architecture reference describing all pluggable layers and the full webhook + polling optimization flow.
  • .env.sample — added WEBHOOK_FALLBACK_TIMEOUT and WEBHOOK_POLL_INTERVAL_MULTIPLIER with explanatory comments; updated NOTES section.

Changed

  • poller.jspollAllServices() now conditionally skips Sonarr/Radarr fetches when webhooks are active; extends TTL of existing cache entries instead of overwriting with empty data.
  • cache.js — exports four new webhook metrics helpers alongside the existing MemoryCache singleton.
  • webhook.js — imported express-rate-limit; added validatePayload(), isReplay(), VALID_EVENT_TYPES allowlist, recentEvents Map, and per-route webhookLimiter middleware.

[1.3.0] - 2026-05-17

Added

  • History tab — new "Recently Completed" tab showing imported and failed downloads from Sonarr/Radarr history for the last N days (configurable via the days input, persisted in localStorage). Auto-refreshes every 5 minutes.
  • History deduplication — when a failed download has subsequently been imported successfully, only the successful record is shown. If the most recent record for an item is a failure but the episode/movie is already on disk (upgrade attempt), the record is flagged as availableForUpgrade.
  • "Upgrade available" badge — failed history cards where the content is already on disk display an amber badge to indicate this is a failed upgrade rather than a missing item.
  • "Hide upgrade failures" toggle — checkbox in the history tab to filter out failed records that are already available on disk. State persists in localStorage. Tooltip explains the behaviour and matches the episode/multi-episode tooltip style.
  • Blocklist & Search button — admin-only button on download cards with an "Import Pending" caution. Removes the download from the client with blocklist=true (preventing re-grab of the same release) then immediately triggers an EpisodeSearch/MoviesSearch command in Sonarr/Radarr. Shows a confirmation dialog, loading/success/error states. Kicks a background poll on success.
  • POST /api/dashboard/blocklist-search — new admin-only endpoint backing the above button. Accepts arrQueueId, arrType, arrInstanceUrl, arrInstanceKey, arrContentId, arrContentType.
  • Title link home navigation — the sofarr logo/title in the header now navigates to the default view (Active Downloads tab, close status panel, reset "Show all users" toggle) without a page reload.
  • Version footer link — the version string in the dashboard footer links to the source repository.

Changed

  • History records are now deduplicated server-side before being sent to the client — only the most relevant record per content item per instance is returned.
  • Import-issue badge tooltip now uses the themed var(--surface) / var(--text-primary) / var(--border) CSS variables, matching the episode and toggle tooltip style.
  • Poller now stores _instanceKey on Sonarr/Radarr queue records in the cache, enabling the backend to look up API credentials for blocklist operations without an additional configuration lookup.
  • Blocklist & Search button — now available on all admin downloads (not just those with import issues), and also available to non-admin users when: import issues are present, OR (for qBittorrent torrents only) the download is more than 1 hour old AND has less than 100% availability.
  • Download object — added canBlocklist boolean field to indicate whether the current user can blocklist a given download.
  • qBittorrent torrent data — added addedOn timestamp field to enable age-based blocklist eligibility checks.

[1.2.2] - 2026-05-17

Changed

  • Header logo — uses the higher-resolution 192px favicon source rendered at 56px for better visual balance alongside the title text.

[1.2.1] - 2026-05-17

Added

  • Version footer — the dashboard footer now displays the running app version (e.g. sofarr v1.2.1), fetched from the /health endpoint on page load.

[1.2.0] - 2025-05-17

Security

  • Docker secrets support — all sensitive environment variables (COOKIE_SECRET, EMBY_API_KEY, SABNZBD_API_KEY, SONARR_API_KEY, RADARR_API_KEY, QBITTORRENT_PASSWORD) now support the standard _FILE variant for loading values from mounted secret files (e.g. COOKIE_SECRET_FILE=/run/secrets/cookie_secret).
  • Weak secret warning — server now warns at startup if COOKIE_SECRET is shorter than 32 characters.
  • EMBY_URL validation — validates the Emby URL scheme at startup and warns on misconfiguration.
  • Improved error sanitizationsanitizeError() now also redacts hostnames from full request URLs that may appear in axios error messages.
  • Graceful shutdownSIGTERM and SIGINT handlers now stop the background poller and drain open HTTP connections before exiting. Prevents data loss and zombie processes on docker stop.

Compliance

  • MIT LICENSE file added to project root.
  • Copyright headers added to key server source files (index.js, poller.js, config.js, sanitizeError.js, loadSecrets.js).
  • security.txt (/.well-known/security.txt) added for responsible disclosure.

Configuration

  • URL validation added to config.js — all configured service instance URLs are validated for scheme (http/https) and well-formedness at startup; malformed URLs emit a warning instead of crashing.

Docker / Deployment

  • docker-compose.yaml updated with commented Option B (Docker secrets _FILE pattern) alongside the existing plain-env Option A.
  • .dockerignore updated — tests/, coverage/, vitest.config.js, CHANGELOG.md, SECURITY.md, LICENSE, .markdownlint.json excluded from the production image.

CI

  • docs-check workflow added — separate Gitea Actions workflow that lints all Markdown files and validates Mermaid diagram syntax on every push that touches .md files. Both jobs use continue-on-error: true so documentation issues never block a release.
  • Mermaid diagrams in docs/ARCHITECTURE.md fixed — replaced invalid \n in stateDiagram transition labels, Unicode arrows/dashes, and double-spaces in flowchart edge definitions.

[1.1.2] - 2025-05-15

Changed

  • Server startup message now includes the current version (sofarr v1.1.2).

[1.1.1] - 2025-05-14

Fixed

  • Docker/TrueNAS SCALE healthcheck: dynamic HTTP/HTTPS selection based on TLS_ENABLED environment variable. Prevents containers from being stuck in "starting" state when TLS_ENABLED=false.

[1.1.0] - 2025-05-13

Added

  • Episode display — TV show download cards now show episode information (S01E01 format with title). Multi-episode packs show a "Multiple episodes" badge with a tooltip listing all episodes.
  • Episode tooltip — solid background colour (theme-dependent) for readability.
  • Sonarr queue and history API requests now include includeEpisode=true.

[1.0.0] - 2025-05-01

Added

  • Initial release.
  • SABnzbd queue and history integration.
  • qBittorrent torrent integration.
  • Sonarr and Radarr queue/history matching with user tag filtering.
  • Emby/Jellyfin authentication.
  • Server-Sent Events (SSE) real-time dashboard.
  • Per-request CSP nonce, CSRF double-submit, HSTS, Permissions-Policy.
  • Background polling with configurable interval and on-demand fallback.
  • Docker multi-stage build, non-root user, read-only filesystem.
  • TLS support with bundled snakeoil certificate.