-
Notify Bridge 0.8.1
Stablereleased this
2026-05-16 18:33:41 +03:00 | 10 commits to master since this releasev0.8.1 (2026-05-16)
⚠️ Breaking Changes
- Telegram webhook secret is now mandatory.
NOTIFY_BRIDGE_TELEGRAM_WEBHOOK_SECRETmust be set when running Telegram bots in webhook mode — the inbound endpoint returns 401 without it. Polling-mode bots are unaffected (10d30fc) - Generic webhooks with
auth_mode="none"require explicit opt-in. Existing unauthenticated webhook providers must setacknowledge_unauthenticated=truein their config or they will be rejected. Generic webhook ingest is also rate-limited to 60 requests/min per source IP (10d30fc) - Every user now gets a
bridge_selfprovider 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)
User-facing changes
Features
- 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 newTagInputUI control (22127e2) - Home Assistant bot commands.
/status,/entities [glob],/state <entity_id>,/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/stateoutput is capped at 30 attributes to stay within Telegram message limits (22127e2) - Bridge self-monitoring (
bridge_selfprovider). 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) - bridge_self bot commands.
/status,/thresholds,/reset,/healthlet operators inspect bridge health and reset counters from chat. Includes Jinja2 templates for both locales (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)
Bug Fixes
- 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 == 0and logsinterval_not_dueskips so operators can distinguish suppressed-by-interval from other skip causes (90f958b) - Planka webhook crash fixed. The handler had a
NameErroron every call when reading the request body — webhook ingest from Planka now works (10d30fc) - HA quiet-hours dropped events.
ha_state_changed,automation_triggered,service_called, andevent_firedwere missing from the deferrable set and were silently dropped during quiet windows. They now defer like every other event type (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) - 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)
- NUT first poll seeds silently. No more spurious
ups_on_batterynotification on the very first poll after adding a NUT provider (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)
Performance
- Jinja2 template compilation cached with
lru_cache(maxsize=512)— repeated renders of the same template no longer reparse the source (10d30fc) - Per-locale render cache in
NotificationDispatcherskips re-rendering identical content for receivers sharing a locale (10d30fc) - Tracker list cached per
provider_idwith a 5s TTL plus explicit invalidation on tracker CRUD — relieves the HA chat-bus rate-query pressure (10d30fc) - Nav-counts collapsed from 16 round-trips to a single
UNION ALL(10d30fc) - HA event_log skips empty events —
assets_added/assets_removedevents with empty payloads are no longer persisted (10d30fc)
Security
- DNS-rebinding SSRF protection.
PinnedResolveris now wired into the shared aiohttp session — outbound URL validation pins the resolved IP for the lifetime of the request (10d30fc) - Mass-assignment guards added on Action create/update; cron expressions with sub-minute granularity are rejected (10d30fc)
- Backup import hardening. JSON depth capped at 10, node count at 100k;
_sanitize_confignow extends to all JSON-typed fields on import (10d30fc) - Telegram
_safe_getwalks redirects manually with SSRF revalidation at every hop (10d30fc) - Bcrypt 72-byte password length cap with a clear
422response (was silently truncating before) (10d30fc) - Webhook payload body redaction. Sensitive substring set extended with
oauth,client_secret,webhook_secret,csrfin both header filter and template-extras filter (10d30fc)
Operations
- Deep healthcheck at
/api/ready— checks DBSELECT 1, scheduler running, HA supervisor presence; returns{ready, checks, errors, version}(10d30fc) - Prometheus metrics at
/api/metrics—deferred_pending,event_log_total,dispatch_duration,poll_failures,send_failures(10d30fc) - New
OPERATIONS.mdcovering deploy, healthchecks, metrics, backup/restore procedures, log handling, common scenarios, and upgrade flow (10d30fc)
Development / Internal
Architecture
- Shared dispatch pipeline. Extracted webhook ingest's event-log + deferred-dispatch + quiet-hours code path from
api/webhooks.pyintoservices/event_dispatch.pyso HA subscription and webhook ingest now share the same pipeline (22127e2) ServiceProviderABC gains optionalsubscribe()for push-style providers;HomeAssistantServiceProvideruses it via a per-provider supervisor task started in the FastAPI lifespan (22127e2)- Provider construction switched from if/elif ladder to factory registry (10d30fc)
- Provider credential resolution unified across all 5 dispatch sites (10d30fc)
NotificationDispatcherhoisted out of the per-tracker loop (10d30fc)
Database
- New UNIQUE indexes:
service_provider.webhook_token,telegram_bot.webhook_path_id, partial UNIQUE ontelegram_bot.bot_id,telegram_chat(bot_id, chat_id),notification_tracker_targetunique link, partial UNIQUE onbridge_selfprovider per user (10d30fc) - Composite
ix_event_log_user_event_type_createdindex (10d30fc) save_chat_from_webhookswitched toON CONFLICT DO UPDATE(was racy under concurrent webhook delivery) (10d30fc)ondelete=CASCADEon user-id FKs (model annotation; app-side cascade delete added for existing data) (10d30fc)delete_notification_trackerconverted from N+1 to bulk DELETE/UPDATE (10d30fc)- Module-level
asyncio.Lockreplaced with lazy_get_lock()pattern (avoids cross-event-loop binding) (10d30fc) - VACUUM INTO snapshot now
PRAGMA integrity_check-verified before being returned (10d30fc) - Batched
receivers/chats/botsinload_link_data(was per-target N+1) (10d30fc) flag_modifiedon JSON column reassignments in deferred_dispatch (10d30fc)
Frontend
- 76
catch (err: any)sites converted toerrMsg(err)helper (10d30fc) globalProviderFiltermade a pure getter; reconciliation moved to a one-time$effectin+layout(10d30fc)- Provider-filter binding simplified — paired
$effects and the_syncingFilterflag removed; now a one-way derived value (10d30fc) entity-cachegot a separate_refreshingflag for background re-fetches so loading spinners don't appear on revalidation (10d30fc)api.ts401 handling rewritten:AuthRedirectErrorclass + dedup_redirectingflag,goto()instead ofwindow.location.href(10d30fc)- Accessibility:
aria-expandedon mobile More menu,role=switch+aria-checkedon Telegram bot toggles (10d30fc) - Provider-specific hardcoding removed: Immich-only block extracted to descriptor
featureDiscoveryHint(10d30fc) - 5
svelte-checknull-narrowing errors fixed inEventDetailModal(10d30fc) - New
TagInputcomponent for free-text glob/domain lists; new toggleConfigFieldtype for HA descriptor (22127e2)
CI/Build
- CI pytest gate added to
.gitea/workflows/build.ymlandrelease.yml(wheel-built install to dodge editable-install slowness on the hosted runner) (10d30fc)
Tests
- 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)
Other
command_syncsnapshot+expunge bot before exitingAsyncSession(was raising on detached-instance access) (10d30fc)- HA
asyncio.shieldnow drains inner task on cancellation (was leaking tasks on supervisor restart) (10d30fc) - APScheduler drain job ID resolution upgraded to seconds (was minute-bucketed; collisions possible) (10d30fc)
- Webhook payload rollback failures now logged (were swallowed) (10d30fc)
All Commits
Hash Message Author 8651767 feat: bridge_self bot commands — status, thresholds, reset, health alexei.dolgolyov 10d30fc feat: production readiness — security, perf, bug fixes, bridge self-monitoring alexei.dolgolyov 22127e2 feat: Home Assistant provider — WebSocket subscription + bot commands alexei.dolgolyov 90f958b fix(server): honor periodic_interval_days for Immich periodic summary alexei.dolgolyov dec0839 feat: on-watch stats scope selector (page vs all) alexei.dolgolyov Changelog
d7c48b0ci: isolate test backend install in venv
66f152efix(tests): green pytest gate for v0.8.1
faaaa39chore: release v0.8.1
8651767feat: bridge_self bot commands — status, thresholds, reset, health
10d30fcfeat: production readiness — security, perf, bug fixes, bridge self-monitoring
22127e2feat: Home Assistant provider — WebSocket subscription + bot commands
90f958bfix(server): honor periodic_interval_days for Immich periodic summary
dec0839feat: on-watch stats scope selector (page vs all)Downloads
- Telegram webhook secret is now mandatory.