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>
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.
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
.vgefiles, 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
- User guides
- Home User Guide — mini PC to working cameras, step by step.
- Operator Guide — configuration, CLI, backups, security, upgrades.
- Camera Hardware Guide — picking and wiring up cameras.
- Architecture
- Overview — process model, MQTT bus, data flow, storage layout.
- Conventions — coding rules for contributors.
- Subsystems — one short reference per subsystem: camera, detection, events, alerts, sensors, ups, storage, highlights, presence, pets, health, web.
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.mdfor the project's code conventions, or the human-friendly distillation atdocs/architecture/conventions.md. - Run
ruff check vigilar/andpytest— both must pass. - Keep commits small and focused.
