adlee-was-taken d17186f466 feat: wire MQTT → SSE bridge so the event timeline updates live
Closes #1.

The Flask event-timeline was dead: `broadcast_sse_event` existed in
`vigilar/web/blueprints/events.py` but had zero call sites. Clients
subscribed to `/events/stream`, received the initial "connected"
message, and then only keepalives — a page refresh was required to
see new events. (Web Push via VAPID was independent and already worked.)

The root cause was a process-boundary gap: the events subsystem runs
in its own OS process and emits to MQTT, while the Flask app runs in a
separate process with no MQTT client of its own.

This change adds a thin bridge:

- EventProcessor._handle_event now publishes a classified summary
  (id, ts, type, severity, source_id, payload) to a new topic
  `Topics.EVENTS_PUBLISHED = "vigilar/events/published"` right after
  `insert_event()`. Classification logic stays in one place.

- A new module `vigilar/web/sse_bridge.py` provides `forward_event`
  (MQTT handler) and `start_sse_bridge(cfg)` (creates a MessageBus,
  subscribes forward_event to EVENTS_PUBLISHED, connects, returns the
  bus).

- `vigilar/main.py:_run_web` starts the bridge after `create_app(cfg)`
  and disconnects it on shutdown. Bridge failure is logged but does
  not kill the web process — the UI still works without live updates.

- `create_app` is deliberately NOT changed. Keeping the bridge out of
  the app factory means no existing test triggers a real MQTT
  connection, and the bridge stays a production-only concern wired by
  the supervisor.

Tests (all added with TDD, RED verified before GREEN):

- tests/unit/test_events.py::TestEventsPublishedBroadcast — asserts
  `_handle_event` publishes the classified payload for a motion event
  and does NOT publish for unclassified topics (heartbeats).
- tests/unit/test_sse_bridge.py — asserts `forward_event` reaches SSE
  subscribers, and `start_sse_bridge` wires the handler to
  `Topics.EVENTS_PUBLISHED` on a connected bus (fake bus, no real
  MQTT in tests).

Also refreshes the docs that previously flagged the dead SSE as a
known limitation (operator guide, web architecture doc).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 11:07:25 -04:00
2026-04-05 10:23:29 -04:00

Vigilar

DIY offline-first home security. Your cameras, your house, your data.

Vigilar runs on a small mini PC in your home, records from your IP cameras, detects motion, and pushes notifications to your phone — with no cloud, no account, and no external service in the critical path.

Vigilar web UI grid view

Why Vigilar

  • Offline-first. No cloud dependency. No external calls in the hot path.
  • Multi-camera live view. HLS grid for bandwidth efficiency, MJPEG single view for low-latency focus.
  • On-device motion detection. OpenCV MOG2 with a 5-second pre-motion ring buffer, so the run-up to a trigger is captured.
  • Adaptive FPS. Cameras idle at 2 FPS and jump to 30 FPS on motion.
  • Event timeline and highlight reels. Plus timelapses and visitor, pet, and wildlife tracking as separate views.
  • Phone push notifications. PWA + VAPID web push. No Firebase, no Google Cloud Messaging.
  • Encrypted recordings at rest. AES-256-CTR .vge files, key stays on your box. (Confidential, not tamper-evident — see the Operator Guide for the details of the threat model.)
  • UPS aware. NUT integration, graceful shutdown on critical battery.
  • Runs on a cheap mini PC. 4 GB RAM, 128 GB SSD, any modern x86_64 box.

Quick paths

I want to… Start here
Set it up at home on a mini PC Home User Guide
Run it as a self-hoster / sysadmin Operator Guide
Understand how it works internally Architecture Overview
Pick cameras that work well with it Camera Hardware Guide

60-second overview

   [IP cameras] ──RTSP──▶  [mini PC running Vigilar]  ──LAN──▶  [phone / browser]
                                      │
                                      └── optional nightly ──▶ [NAS]
  • Language: Python 3.11+
  • Web: Flask + Bootstrap 5 dark + hls.js
  • Storage: SQLite (WAL) via SQLAlchemy Core
  • Bus: Local Mosquitto on 127.0.0.1:1883
  • Video: OpenCV (capture + motion), FFmpeg subprocess (recording, HLS)

Status

Alpha. Works on the author's hardware. Expect rough edges and breaking changes. Not recommended for anyone who cannot read a stack trace.

Known limitations worth calling out up front:

  • The in-browser event timeline does not yet update live. Push notifications to your phone work in real time; the timeline itself needs a page refresh.
  • Recording encryption is AES-256-CTR: confidential at rest, but not tamper-evident.
  • There is no automated database migration framework yet. Schema changes are forward-only.

See the Operator Guide for the full list.

Installation (TL;DR)

git clone <repo URL> vigilar
cd vigilar
sudo ./scripts/install.sh
sudo systemctl enable --now vigilar

Then open http://<mini-pc-ip>:49735 on your LAN.

For the full walkthrough (OS install, camera setup, PIN, push notifications, NAS backup), see the Home User Guide.

For configuration, CLI, secrets, backups, and upgrades, see the Operator Guide.

Documentation

License

Vigilar is distributed under the GNU General Public License v3.0. See LICENSE for the full text. (If the LICENSE file is not yet in the tree, it will be added in a follow-up.)

Contributing

Issues and pull requests are welcome. Before sending a PR:

  • Read CLAUDE.md for the project's code conventions, or the human-friendly distillation at docs/architecture/conventions.md.
  • Run ruff check vigilar/ and pytest — both must pass.
  • Keep commits small and focused.
Description
Local AI-model assisted home security and surveillance system for those that don't trust that data to the cloud.
Readme 851 KiB
Languages
Python 63.7%
HTML 21.6%
Shell 8.2%
JavaScript 5.5%
CSS 1%