diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index e382bf6..ae97f34 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -1,16 +1,53 @@ -# v0.8.0 (2026-05-12) +# v0.8.1 (2026-05-16) + +## ⚠️ Breaking Changes + +- **Telegram webhook secret is now mandatory.** `NOTIFY_BRIDGE_TELEGRAM_WEBHOOK_SECRET` must be set when running Telegram bots in webhook mode — the inbound endpoint returns 401 without it. Polling-mode bots are unaffected ([10d30fc](https://git.dolgolyov-family.by/alexei.dolgolyov/notify-bridge/commit/10d30fc)) +- **Generic webhooks with `auth_mode="none"` require explicit opt-in.** Existing unauthenticated webhook providers must set `acknowledge_unauthenticated=true` in their config or they will be rejected. Generic webhook ingest is also rate-limited to 60 requests/min per source IP ([10d30fc](https://git.dolgolyov-family.by/alexei.dolgolyov/notify-bridge/commit/10d30fc)) +- **Every user now gets a `bridge_self` provider auto-seeded.** It is internal-only and excluded from the "create provider" wizard, but appears in the provider list. Wire it to a Telegram/Email/Matrix target to receive bridge health alerts ([10d30fc](https://git.dolgolyov-family.by/alexei.dolgolyov/notify-bridge/commit/10d30fc)) ## User-facing changes ### Features -- **Quiet hours now defer notifications instead of dropping them.** Events that arrive during a tracker's quiet window are stored on disk and re-fired at the window end. Asset events for the same `(link, event_type, collection)` coalesce so a flurry of adds/removes during the night collapses into a single morning notification ([ba199f2](https://git.dolgolyov-family.by/alexei.dolgolyov/notify-bridge/commit/ba199f2)) -- **Upstream release check.** New "Release Cassette" in Settings polls a configurable Gitea or GitHub repo on a schedule and surfaces the latest tag in the UI so you know when a newer Notify Bridge is available. Pre-release filtering and interval are operator-configurable; the install ships pointed at this repo's own upstream ([ba199f2](https://git.dolgolyov-family.by/alexei.dolgolyov/notify-bridge/commit/ba199f2)) -- **Frontend polish across the board.** New `MetaStrip` component, expanded `EventDetailModal`, and i18n additions land alongside cohesive Aurora-glass styling tweaks on most management pages — providers, targets, bots, trackers, command and notification templates, users, actions, layout, and dashboard ([ba199f2](https://git.dolgolyov-family.by/alexei.dolgolyov/notify-bridge/commit/ba199f2)) +- **Home Assistant provider.** New service provider that subscribes to a Home Assistant instance over WebSocket (long-lived connection with auth handshake, exponential-backoff reconnect, area-registry enrichment) and emits 4 event types: `state_changed`, `automation_triggered`, `call_service`, `event_fired`. Trackers support entity-glob, domain-allowlist, and exact-id filters via the new `TagInput` UI control ([22127e2](https://git.dolgolyov-family.by/alexei.dolgolyov/notify-bridge/commit/22127e2)) +- **Home Assistant bot commands.** `/status`, `/entities [glob]`, `/state `, `/areas` — query your HA instance from chat. Multi-command WS sessions reuse a single handshake; sensitive attributes (camera access tokens, entity pictures, etc.) are blocklisted and `/state` output is capped at 30 attributes to stay within Telegram message limits ([22127e2](https://git.dolgolyov-family.by/alexei.dolgolyov/notify-bridge/commit/22127e2)) +- **Bridge self-monitoring (`bridge_self` provider).** A new internal provider type emits health events from the bridge itself: `bridge_self_poll_failures` (consecutive tracker poll failures), `bridge_self_deferred_backlog` (pending defers above threshold), `bridge_self_target_failures` (consecutive 5xx/network failures per target). Per-user thresholds default to 3 / 100 / 5 and are configurable. Self-loop guard ensures bridge_self failures never count toward target-failure thresholds ([10d30fc](https://git.dolgolyov-family.by/alexei.dolgolyov/notify-bridge/commit/10d30fc)) +- **bridge_self bot commands.** `/status`, `/thresholds`, `/reset`, `/health` let operators inspect bridge health and reset counters from chat. Includes Jinja2 templates for both locales ([8651767](https://git.dolgolyov-family.by/alexei.dolgolyov/notify-bridge/commit/8651767)) +- **On-watch stats scope selector.** New icon toggle on the "On watch" provider deck switches between page-scoped stats (legacy) and full-corpus stats that aggregate across every event matching the active filters ([dec0839](https://git.dolgolyov-family.by/alexei.dolgolyov/notify-bridge/commit/dec0839)) -### Documentation +### Bug Fixes -- README rewritten to cover every supported provider, target type, bot command, and smart action — including the deploy / env-var matrix ([bb5afcc](https://git.dolgolyov-family.by/alexei.dolgolyov/notify-bridge/commit/bb5afcc), [4335036](https://git.dolgolyov-family.by/alexei.dolgolyov/notify-bridge/commit/4335036)) +- **Immich periodic summary now honors `periodic_interval_days`.** A configured 3-day interval was firing every day because the dispatch path never consulted the interval or start date. The scheduler now gates on `(today - start_date).days % interval == 0` and logs `interval_not_due` skips so operators can distinguish suppressed-by-interval from other skip causes ([90f958b](https://git.dolgolyov-family.by/alexei.dolgolyov/notify-bridge/commit/90f958b)) +- **Planka webhook crash fixed.** The handler had a `NameError` on every call when reading the request body — webhook ingest from Planka now works ([10d30fc](https://git.dolgolyov-family.by/alexei.dolgolyov/notify-bridge/commit/10d30fc)) +- **HA quiet-hours dropped events.** `ha_state_changed`, `automation_triggered`, `service_called`, and `event_fired` were missing from the deferrable set and were silently dropped during quiet windows. They now defer like every other event type ([10d30fc](https://git.dolgolyov-family.by/alexei.dolgolyov/notify-bridge/commit/10d30fc)) +- **Notifier no longer cancels peer sends on a single failure.** Switched the per-receiver fan-out to `asyncio.gather(return_exceptions=True)`; one bad chat won't cancel sends to the rest ([10d30fc](https://git.dolgolyov-family.by/alexei.dolgolyov/notify-bridge/commit/10d30fc)) +- **Quiet-hours gate now respects event-type-disabled.** When a tracker has the event type disabled, that wins over the deferral path — events are dropped, not stored to be drained later ([10d30fc](https://git.dolgolyov-family.by/alexei.dolgolyov/notify-bridge/commit/10d30fc)) +- **NUT first poll seeds silently.** No more spurious `ups_on_battery` notification on the very first poll after adding a NUT provider ([10d30fc](https://git.dolgolyov-family.by/alexei.dolgolyov/notify-bridge/commit/10d30fc)) +- **HA disconnect/reconnect now logged.** Status changes write `ha_status_*` rows to the EventLog so operators can see WS supervisor health in the UI ([10d30fc](https://git.dolgolyov-family.by/alexei.dolgolyov/notify-bridge/commit/10d30fc)) + +### Performance + +- **Jinja2 template compilation cached** with `lru_cache(maxsize=512)` — repeated renders of the same template no longer reparse the source ([10d30fc](https://git.dolgolyov-family.by/alexei.dolgolyov/notify-bridge/commit/10d30fc)) +- **Per-locale render cache in `NotificationDispatcher`** skips re-rendering identical content for receivers sharing a locale ([10d30fc](https://git.dolgolyov-family.by/alexei.dolgolyov/notify-bridge/commit/10d30fc)) +- **Tracker list cached per `provider_id`** with a 5s TTL plus explicit invalidation on tracker CRUD — relieves the HA chat-bus rate-query pressure ([10d30fc](https://git.dolgolyov-family.by/alexei.dolgolyov/notify-bridge/commit/10d30fc)) +- **Nav-counts collapsed from 16 round-trips to a single `UNION ALL`** ([10d30fc](https://git.dolgolyov-family.by/alexei.dolgolyov/notify-bridge/commit/10d30fc)) +- **HA event_log skips empty events** — `assets_added`/`assets_removed` events with empty payloads are no longer persisted ([10d30fc](https://git.dolgolyov-family.by/alexei.dolgolyov/notify-bridge/commit/10d30fc)) + +### Security + +- **DNS-rebinding SSRF protection.** `PinnedResolver` is now wired into the shared aiohttp session — outbound URL validation pins the resolved IP for the lifetime of the request ([10d30fc](https://git.dolgolyov-family.by/alexei.dolgolyov/notify-bridge/commit/10d30fc)) +- **Mass-assignment guards** added on Action create/update; cron expressions with sub-minute granularity are rejected ([10d30fc](https://git.dolgolyov-family.by/alexei.dolgolyov/notify-bridge/commit/10d30fc)) +- **Backup import hardening.** JSON depth capped at 10, node count at 100k; `_sanitize_config` now extends to all JSON-typed fields on import ([10d30fc](https://git.dolgolyov-family.by/alexei.dolgolyov/notify-bridge/commit/10d30fc)) +- **Telegram `_safe_get` walks redirects manually** with SSRF revalidation at every hop ([10d30fc](https://git.dolgolyov-family.by/alexei.dolgolyov/notify-bridge/commit/10d30fc)) +- **Bcrypt 72-byte password length cap** with a clear `422` response (was silently truncating before) ([10d30fc](https://git.dolgolyov-family.by/alexei.dolgolyov/notify-bridge/commit/10d30fc)) +- **Webhook payload body redaction.** Sensitive substring set extended with `oauth`, `client_secret`, `webhook_secret`, `csrf` in both header filter and template-extras filter ([10d30fc](https://git.dolgolyov-family.by/alexei.dolgolyov/notify-bridge/commit/10d30fc)) + +### Operations + +- **Deep healthcheck at `/api/ready`** — checks DB `SELECT 1`, scheduler running, HA supervisor presence; returns `{ready, checks, errors, version}` ([10d30fc](https://git.dolgolyov-family.by/alexei.dolgolyov/notify-bridge/commit/10d30fc)) +- **Prometheus metrics at `/api/metrics`** — `deferred_pending`, `event_log_total`, `dispatch_duration`, `poll_failures`, `send_failures` ([10d30fc](https://git.dolgolyov-family.by/alexei.dolgolyov/notify-bridge/commit/10d30fc)) +- **New `OPERATIONS.md`** covering deploy, healthchecks, metrics, backup/restore procedures, log handling, common scenarios, and upgrade flow ([10d30fc](https://git.dolgolyov-family.by/alexei.dolgolyov/notify-bridge/commit/10d30fc)) --- @@ -18,14 +55,50 @@ ### Architecture -- New `deferred_dispatch` table with two migrations: an `ON DELETE SET NULL` FK rebuild on `event_log_id` (so the daily event-log retention sweep no longer deadlocks against pending defers), and a partial unique index on `(link_id, collection_id, event_type) WHERE status='pending'` to make coalescing race-safe under SQLite's serializable writes ([ba199f2](https://git.dolgolyov-family.by/alexei.dolgolyov/notify-bridge/commit/ba199f2)) -- Drain scheduler with three layers: a one-shot APScheduler `date` job per window-end (idempotent, minute-bucketed), a 5-minute periodic catch-up scan as safety net for misfire-grace overflow and process-restart gaps, and `load_pending_drain_jobs` to re-arm scheduled drains on boot ([ba199f2](https://git.dolgolyov-family.by/alexei.dolgolyov/notify-bridge/commit/ba199f2)) -- Release-check provider abstraction (`packages/core/.../release/`) with Gitea and GitHub adapters, SSRF-safe outbound URL validation, a registry/factory, and a server-side scheduler probe with cached state and on-settings-change cache invalidation ([ba199f2](https://git.dolgolyov-family.by/alexei.dolgolyov/notify-bridge/commit/ba199f2)) -- Version resolution helper (`packages/server/.../version.py`) that returns the max of installed-package metadata vs source `pyproject.toml` — fixes the long-running editable-install bug where bumping the version without reinstalling kept the old number visible in the UI ([ba199f2](https://git.dolgolyov-family.by/alexei.dolgolyov/notify-bridge/commit/ba199f2)) +- **Shared dispatch pipeline.** Extracted webhook ingest's event-log + deferred-dispatch + quiet-hours code path from `api/webhooks.py` into `services/event_dispatch.py` so HA subscription and webhook ingest now share the same pipeline ([22127e2](https://git.dolgolyov-family.by/alexei.dolgolyov/notify-bridge/commit/22127e2)) +- **`ServiceProvider` ABC gains optional `subscribe()`** for push-style providers; `HomeAssistantServiceProvider` uses it via a per-provider supervisor task started in the FastAPI lifespan ([22127e2](https://git.dolgolyov-family.by/alexei.dolgolyov/notify-bridge/commit/22127e2)) +- **Provider construction switched from if/elif ladder to factory registry** ([10d30fc](https://git.dolgolyov-family.by/alexei.dolgolyov/notify-bridge/commit/10d30fc)) +- **Provider credential resolution unified** across all 5 dispatch sites ([10d30fc](https://git.dolgolyov-family.by/alexei.dolgolyov/notify-bridge/commit/10d30fc)) +- **`NotificationDispatcher` hoisted out of the per-tracker loop** ([10d30fc](https://git.dolgolyov-family.by/alexei.dolgolyov/notify-bridge/commit/10d30fc)) + +### Database + +- **New UNIQUE indexes:** `service_provider.webhook_token`, `telegram_bot.webhook_path_id`, partial UNIQUE on `telegram_bot.bot_id`, `telegram_chat(bot_id, chat_id)`, `notification_tracker_target` unique link, partial UNIQUE on `bridge_self` provider per user ([10d30fc](https://git.dolgolyov-family.by/alexei.dolgolyov/notify-bridge/commit/10d30fc)) +- **Composite `ix_event_log_user_event_type_created` index** ([10d30fc](https://git.dolgolyov-family.by/alexei.dolgolyov/notify-bridge/commit/10d30fc)) +- **`save_chat_from_webhook` switched to `ON CONFLICT DO UPDATE`** (was racy under concurrent webhook delivery) ([10d30fc](https://git.dolgolyov-family.by/alexei.dolgolyov/notify-bridge/commit/10d30fc)) +- **`ondelete=CASCADE` on user-id FKs** (model annotation; app-side cascade delete added for existing data) ([10d30fc](https://git.dolgolyov-family.by/alexei.dolgolyov/notify-bridge/commit/10d30fc)) +- **`delete_notification_tracker` converted from N+1 to bulk DELETE/UPDATE** ([10d30fc](https://git.dolgolyov-family.by/alexei.dolgolyov/notify-bridge/commit/10d30fc)) +- **Module-level `asyncio.Lock` replaced with lazy `_get_lock()` pattern** (avoids cross-event-loop binding) ([10d30fc](https://git.dolgolyov-family.by/alexei.dolgolyov/notify-bridge/commit/10d30fc)) +- **VACUUM INTO snapshot now `PRAGMA integrity_check`-verified** before being returned ([10d30fc](https://git.dolgolyov-family.by/alexei.dolgolyov/notify-bridge/commit/10d30fc)) +- **Batched `receivers/chats/bots` in `load_link_data`** (was per-target N+1) ([10d30fc](https://git.dolgolyov-family.by/alexei.dolgolyov/notify-bridge/commit/10d30fc)) +- **`flag_modified` on JSON column reassignments** in deferred_dispatch ([10d30fc](https://git.dolgolyov-family.by/alexei.dolgolyov/notify-bridge/commit/10d30fc)) + +### Frontend + +- **76 `catch (err: any)` sites converted to `errMsg(err)` helper** ([10d30fc](https://git.dolgolyov-family.by/alexei.dolgolyov/notify-bridge/commit/10d30fc)) +- **`globalProviderFilter` made a pure getter**; reconciliation moved to a one-time `$effect` in `+layout` ([10d30fc](https://git.dolgolyov-family.by/alexei.dolgolyov/notify-bridge/commit/10d30fc)) +- **Provider-filter binding simplified** — paired `$effect`s and the `_syncingFilter` flag removed; now a one-way derived value ([10d30fc](https://git.dolgolyov-family.by/alexei.dolgolyov/notify-bridge/commit/10d30fc)) +- **`entity-cache` got a separate `_refreshing` flag** for background re-fetches so loading spinners don't appear on revalidation ([10d30fc](https://git.dolgolyov-family.by/alexei.dolgolyov/notify-bridge/commit/10d30fc)) +- **`api.ts` 401 handling rewritten:** `AuthRedirectError` class + dedup `_redirecting` flag, `goto()` instead of `window.location.href` ([10d30fc](https://git.dolgolyov-family.by/alexei.dolgolyov/notify-bridge/commit/10d30fc)) +- **Accessibility:** `aria-expanded` on mobile More menu, `role=switch` + `aria-checked` on Telegram bot toggles ([10d30fc](https://git.dolgolyov-family.by/alexei.dolgolyov/notify-bridge/commit/10d30fc)) +- **Provider-specific hardcoding removed:** Immich-only block extracted to descriptor `featureDiscoveryHint` ([10d30fc](https://git.dolgolyov-family.by/alexei.dolgolyov/notify-bridge/commit/10d30fc)) +- **5 `svelte-check` null-narrowing errors fixed** in `EventDetailModal` ([10d30fc](https://git.dolgolyov-family.by/alexei.dolgolyov/notify-bridge/commit/10d30fc)) +- **New `TagInput` component** for free-text glob/domain lists; new toggle `ConfigField` type for HA descriptor ([22127e2](https://git.dolgolyov-family.by/alexei.dolgolyov/notify-bridge/commit/22127e2)) + +### CI/Build + +- **CI pytest gate added** to `.gitea/workflows/build.yml` and `release.yml` (wheel-built install to dodge editable-install slowness on the hosted runner) ([10d30fc](https://git.dolgolyov-family.by/alexei.dolgolyov/notify-bridge/commit/10d30fc)) ### Tests -- New test suites: `test_deferred_dispatch.py` (drain + coalescing + retention interaction), `test_release_provider.py` (Gitea and GitHub adapter parsing and error paths), and `test_release_service.py` (scheduler-level caching and settings invalidation) ([ba199f2](https://git.dolgolyov-family.by/alexei.dolgolyov/notify-bridge/commit/ba199f2)) +- **New test suites:** `test_bridge_self` (11), `test_gitea_parser` (9), `test_planka_parser` (6), `test_immich_change_detector` (6), `test_backup_roundtrip` (1) ([10d30fc](https://git.dolgolyov-family.by/alexei.dolgolyov/notify-bridge/commit/10d30fc)) + +### Other + +- **`command_sync` snapshot+expunge bot before exiting `AsyncSession`** (was raising on detached-instance access) ([10d30fc](https://git.dolgolyov-family.by/alexei.dolgolyov/notify-bridge/commit/10d30fc)) +- **HA `asyncio.shield` now drains inner task on cancellation** (was leaking tasks on supervisor restart) ([10d30fc](https://git.dolgolyov-family.by/alexei.dolgolyov/notify-bridge/commit/10d30fc)) +- **APScheduler drain job ID resolution upgraded to seconds** (was minute-bucketed; collisions possible) ([10d30fc](https://git.dolgolyov-family.by/alexei.dolgolyov/notify-bridge/commit/10d30fc)) +- **Webhook payload rollback failures now logged** (were swallowed) ([10d30fc](https://git.dolgolyov-family.by/alexei.dolgolyov/notify-bridge/commit/10d30fc)) --- @@ -33,9 +106,11 @@ All Commits | Hash | Message | Author | -|------|---------|--------| -| [ba199f2](https://git.dolgolyov-family.by/alexei.dolgolyov/notify-bridge/commit/ba199f2) | feat: deferred dispatch, release-check provider, settings polish | alexei.dolgolyov | -| [bb5afcc](https://git.dolgolyov-family.by/alexei.dolgolyov/notify-bridge/commit/bb5afcc) | docs: expand README with all providers, targets, bot commands, and smart actions | alexei.dolgolyov | -| [4335036](https://git.dolgolyov-family.by/alexei.dolgolyov/notify-bridge/commit/4335036) | docs: sync README deploy section with actual env vars | alexei.dolgolyov | +| ---- | ------- | ------ | +| [8651767](https://git.dolgolyov-family.by/alexei.dolgolyov/notify-bridge/commit/8651767) | feat: bridge_self bot commands — status, thresholds, reset, health | alexei.dolgolyov | +| [10d30fc](https://git.dolgolyov-family.by/alexei.dolgolyov/notify-bridge/commit/10d30fc) | feat: production readiness — security, perf, bug fixes, bridge self-monitoring | alexei.dolgolyov | +| [22127e2](https://git.dolgolyov-family.by/alexei.dolgolyov/notify-bridge/commit/22127e2) | feat: Home Assistant provider — WebSocket subscription + bot commands | alexei.dolgolyov | +| [90f958b](https://git.dolgolyov-family.by/alexei.dolgolyov/notify-bridge/commit/90f958b) | fix(server): honor periodic_interval_days for Immich periodic summary | alexei.dolgolyov | +| [dec0839](https://git.dolgolyov-family.by/alexei.dolgolyov/notify-bridge/commit/dec0839) | feat: on-watch stats scope selector (page vs all) | alexei.dolgolyov | diff --git a/frontend/package.json b/frontend/package.json index 47bf5a5..351c30f 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,7 +1,7 @@ { "name": "notify-bridge-frontend", "private": true, - "version": "0.8.0", + "version": "0.8.1", "type": "module", "scripts": { "dev": "vite dev", diff --git a/packages/core/pyproject.toml b/packages/core/pyproject.toml index 5bef299..b34382a 100644 --- a/packages/core/pyproject.toml +++ b/packages/core/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "notify-bridge-core" -version = "0.8.0" +version = "0.8.1" description = "Core library for Notify Bridge — service provider abstractions, models, notifications, and templates" requires-python = ">=3.12" dependencies = [ diff --git a/packages/server/pyproject.toml b/packages/server/pyproject.toml index 9399d1f..3f35dd4 100644 --- a/packages/server/pyproject.toml +++ b/packages/server/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "notify-bridge-server" -version = "0.8.0" +version = "0.8.1" description = "Standalone Notify Bridge server — FastAPI REST API with SQLite database" requires-python = ">=3.12" dependencies = [