All checks were successful
Build and Push Docker Image / build (push) Successful in 41s
Docs Check / Markdown lint (push) Successful in 48s
Licence Check / Licence compatibility and copyright header verification (push) Successful in 57s
CI / Security audit (push) Successful in 1m23s
CI / Tests & coverage (push) Successful in 1m36s
Docs Check / Mermaid diagram parse check (push) Successful in 1m43s
169 lines
12 KiB
Markdown
169 lines
12 KiB
Markdown
# Changelog
|
||
|
||
All notable changes to this project will be documented in this file.
|
||
Format follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||
This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||
|
||
---
|
||
|
||
## [1.4.0] - 2026-05-19
|
||
|
||
### Added
|
||
|
||
#### Webhook Integration (Phases 1–5.1)
|
||
|
||
- **Webhook receiver endpoints** — `POST /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 proxy** — `GET /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 setup** — `POST /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 tracking** — `cache.js` now maintains per-instance and global webhook metrics (`lastWebhookTimestamp`, `eventsReceived`, `pollsSkipped`) via `getWebhookMetrics()`, `updateWebhookMetrics()`, `incrementPollsSkipped()`, `getGlobalWebhookMetrics()`.
|
||
- **Conditional poll skipping** — `poller.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 connection** — `webhook.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 validation** — `validatePayload()` 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 protection** — `isReplay()` 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.js` — `pollAllServices()` 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 sanitization** — `sanitizeError()` now also redacts hostnames from full request URLs that may appear in axios error messages.
|
||
- **Graceful shutdown** — `SIGTERM` 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.
|