Architecture
ChannelWatch follows a component-based architecture where each concern is isolated into a distinct layer. The six components communicate through well-defined interfaces, which makes it straightforward to add new alert types or notification providers without touching unrelated code.
Component overview
Section titled “Component overview”graph TD DVR["Channels DVR Server\n(SSE event stream)"] BE["Core Backend\nAsync event monitor"] AS["Alert System\nEvent processors"] NS["Notification System\nProvider dispatch"] WUI["Web UI\nNext.js + FastAPI"] CS["Configuration System\nsettings.json + SQLite"] EF["Extension Framework\nPlugin loader"]
DVR -->|"SSE /dvr/events"| BE BE -->|"parsed events"| AS AS -->|"alert payloads"| NS NS -->|"HTTP delivery"| Providers["Pushover / Discord /\nTelegram / Slack /\nEmail / Gotify /\nMatrix / Custom Apprise"] WUI -->|"read/write config"| CS CS -->|"settings"| BE CS -->|"settings"| AS CS -->|"credentials"| NS EF -->|"registers handlers"| AS EF -->|"registers providers"| NS WUI -->|"diagnostics API"| BECore Backend
Section titled “Core Backend”The Core Backend connects to the Channels DVR server-sent event (SSE) stream at /dvr/events and processes the raw event feed. In v1.0 this layer is fully async, which means it can monitor multiple DVR servers concurrently from a single container without spawning extra threads per server.
Key responsibilities:
- Maintain a persistent SSE connection to each configured DVR server
- Parse raw event payloads into typed event objects
- Route events to the Alert System for processing
- Expose a health endpoint (
/healthz/live,/healthz/ready,/healthz/startup) for Kubernetes probes - Write activity history to the SQLite database at
/config/channelwatch.db
Web UI
Section titled “Web UI”The Web UI is a Next.js frontend backed by a FastAPI server running inside the same container. It serves the dashboard at port 8501 and provides:
- Real-time stream status and disk space overview
- DVR server management (add, edit, soft-delete with 30-day undo)
- Alert toggle controls per event type per DVR
- Notification provider configuration with encrypted credential storage
- Diagnostics panel with connection tests and test-send buttons
- Delivery log for reviewing recent notification history
The UI communicates with the Core Backend through the FastAPI layer. It does not write directly to the SQLite database.
Alert System
Section titled “Alert System”The Alert System receives parsed events from the Core Backend and decides whether to fire a notification. Each alert type is an independent module:
| Alert module | Triggers on |
|---|---|
| Channel Watching | Live TV stream start / end |
| VOD Watching | On-demand playback start (one notification per session) |
| Recording Events | Scheduled, started, completed, cancelled, stopped |
| Disk Space | Free space drops below warning or critical threshold |
The Alert System handles session deduplication (preventing duplicate notifications for the same viewing session), cooldown enforcement for disk alerts, and per-DVR event isolation so a recording on DVR-A never triggers a notification routed to DVR-B’s provider.
Notification System
Section titled “Notification System”The Notification System receives alert payloads and dispatches them to the configured provider. Two delivery paths exist:
- Pushover (native direct API): sends rich notifications with title, message, image, and priority directly to the Pushover API
- Apprise (all other providers): wraps Discord, Telegram, Slack, Email, Gotify, Matrix, and any custom Apprise-compatible URL
Credentials are encrypted at rest using the key at /config/encryption.key. The key is auto-generated on first startup with chmod 0600 permissions. If the key file is missing on a subsequent startup, ChannelWatch refuses to start rather than silently decrypting with a wrong key.
Configuration System
Section titled “Configuration System”All persistent settings live in /config/settings.json. The Configuration System:
- Loads settings on startup and watches for changes (hot reload in v1.0)
- Validates settings against a typed schema before applying them
- Writes a backup to
/config/backups/before any migration step - Exposes a read/write API to the Web UI
- Accepts a subset of settings via environment variables for automation (env vars take precedence over
settings.jsonfor the fields they cover)
The SQLite database at /config/channelwatch.db stores activity history (stream events, notification delivery records) separately from configuration. This separation means you can reset configuration without losing history, and vice versa.
Extension Framework
Section titled “Extension Framework”The Extension Framework is the plugin layer that makes it possible to add new alert types and notification providers without modifying the core codebase. Plugins are Python modules placed in /config/plugins/ and discovered automatically on startup.
A plugin can register:
- A new alert handler (subclass of the base alert class) to process a new event type
- A new notification provider (subclass of the base provider class) to deliver to a new service
Data flow summary
Section titled “Data flow summary”A typical channel-watching notification follows this path:
- Channels DVR emits a
1-file-*SSE event when a stream starts - Core Backend parses the event and extracts channel, program, device, and stream metadata
- Alert System checks whether Channel Watching alerts are enabled for that DVR, deduplicates against active sessions, and builds an alert payload
- Notification System looks up the configured provider for that DVR, encrypts nothing (credentials are already decrypted in memory), and dispatches the payload
- The provider (e.g. Pushover) returns HTTP 200; the Notification System writes a delivery record to SQLite
- The Web UI’s Delivery Log reflects the new record on the next poll
v1.0 design decisions
Section titled “v1.0 design decisions”Single-container model. ChannelWatch runs as one container with supervisor managing the Python backend and the Node.js UI server. This keeps deployment simple for home lab users. Multi-replica HA is on the roadmap but is not part of v1.0.
SQLite over a network database. Activity history and delivery logs are stored in SQLite rather than PostgreSQL or Redis. For a single-container home lab tool, SQLite is faster to set up, requires no external service, and is reliable enough for the write volume ChannelWatch generates.
Async event loop. The v1.0 rewrite replaced the threading model with an async event loop (asyncio). This allows the Core Backend to hold open SSE connections to multiple DVR servers without the overhead of one thread per connection.
Encrypted credentials at rest. API keys and provider credentials are encrypted using the auto-generated key at /config/encryption.key. The encryption prevents accidental plaintext exposure if the config directory is backed up or shared. It is not a substitute for filesystem access control on the host.