You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
User-facing + contributor docs for the integration shipped in Phases 1–3
(+1b). No code/behavior change.
- New /docs page "Home Assistant" (frontend/src/pages/docs/HomeAssistant.jsx)
wired into DocsPage (import, scrollspy section, Integrations sidebar group,
render order): what you get, the 3-step setup (generate osi_ key → install
HACS component → add + paste URL/key), and notes (LAN-direct video, revoke,
MCP-vs-HA key distinction).
- AGENTS.md: integration.py in the structure tree; an "Integration API key"
auth subsection (osi_, kind filter, one-shot session); kind column on the
McpApiKey model row; integration.py in the router-prefix table; full
/api/integration/* endpoint block.
- README.md: feature bullet + integration-key note in the auth paragraph.
- plans/home-assistant-integration.md: status table (Phases 1–3 + 1b shipped,
Phase 4 built in the separate repo, 2b deferred).
- rebuilt backend/static bundle.
Lint clean, vitest 99 passed, build clean.
via `core/integration_auth.py::require_integration_org`:
276
+
1. SHA-256 hash the raw key
277
+
2. Match against `McpApiKey.key_hash` with `revoked=False` AND `kind="integration"`
278
+
3. Derive `org_id` from the matched row
279
+
280
+
Integration keys share the `mcp_api_keys` table with MCP keys, split by `kind`
281
+
(`mcp` vs `integration`). The `kind` filter is applied to **every**`McpApiKey`
282
+
query — both auth paths and both management list/revoke endpoints — so the two
283
+
key kinds can never cross surfaces. `require_integration_org` uses a **one-shot
284
+
DB session** (not `Depends(get_db)`) so the long-lived motion SSE doesn't pin a
285
+
connection for its lifetime. No plan gate — available on all tiers.
286
+
270
287
## Data Models
271
288
272
289
All 20 models in `backend/app/models/models.py`. Every model has `org_id` for tenant isolation EXCEPT `ProcessedWebhook` (global webhook dedup, keyed on Svix msg_id) and `EmailSuppression` (operator-global bounce / complaint list, intentionally cross-tenant so a hard-bounced address stops being mailed for every org).
@@ -279,7 +296,7 @@ All 20 models in `backend/app/models/models.py`. Every model has `org_id` for te
279
296
|`Setting`|`key`, `value`| Per-org key/value store. Used for plan slug, recording config, email toggles, motion email cooldown anchors (`motion_email_cooldown_start:{camera_id}`), CloudNode disk debounce (`cloudnode_disk_low_emit_at:{node_id}`), payment past-due flags. |
-`GET /logs/stats` — summary counts for logs (admin)
390
408
409
+
**integration.py** (prefix `/api/integration`) — Home Assistant; key mgmt is admin (Clerk JWT), data plane is `osi_` integration-key auth (all tiers):
410
+
-`POST /keys` — mint an `osi_` integration key; returns plaintext once (admin)
411
+
-`GET /keys` — list integration keys (admin)
412
+
-`DELETE /keys/{key_id}` — revoke (admin)
413
+
-`GET /cameras` — all cameras across all nodes with LAN-direct `local_url`, snapshot URL, recording state (integration key)
414
+
-`GET /cameras/{id}/snapshot` — live JPEG via the node round-trip (integration key)
415
+
-`POST /cameras/{id}/recording` — toggle `continuous_24_7`; body `{recording: bool}` (integration key)
416
+
-`GET /status` — org rollup: camera/node online counts, per-node disk + version, plan (integration key)
417
+
-`GET /motion/stream` — SSE motion feed for HA `binary_sensor`s; separate subscriber pool from the dashboard's `/api/motion/events/stream` (integration key)
Copy file name to clipboardExpand all lines: README.md
+2-1Lines changed: 2 additions & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -32,6 +32,7 @@ Sentinel Command Center is the cloud hub for the Sentinel ecosystem (Sentinel by
32
32
-**Email notifications** via Resend — opt-in per-org per-kind, with per-camera cooldown + digest mode for high-volume motion events (14 notification kinds gated by 7 setting toggles; plus a hidden `welcome` kind that has no UI toggle)
33
33
- Audit logging for stream access, admin actions, and MCP tool calls
34
34
- MCP server exposing 23 tools (16 read, 7 write) so AI clients can view cameras, file incident reports with snapshots and short video clips, and read back past investigations — with per-key scoping (all / readonly / custom allow-list)
35
+
-**Home Assistant integration** — connect Home Assistant with a single key; every camera across every node appears with live video (LAN-direct, so it never counts against the viewer-hour cap), snapshots, recording control, and real-time motion triggers. Available on **every plan**. REST surface under `/api/integration/*`, consumed by a HACS custom component (separate repo)
35
36
-**Sentinel AI agent** — webhook-driven serverless agent ([separate repo](https://github.com/SourceBox-LLC/SourceBox-Sentinel)) that auto-investigates motion events and incident_opened notifications using a vision-capable LLM ↔ MCP tool loop. Pro: 100 runs/month. Pro Plus: 500 runs/month. Per-org scoping via signed override header — one deployed agent serves every org with no cross-tenant state.
36
37
37
38
---
@@ -110,7 +111,7 @@ The scripts detect which clients you already have and merge an `opensentry` entr
110
111
111
112
**Video pipeline:** CloudNode transcodes USB camera video into HLS segments and pushes each `.ts` file directly to the backend via `POST /api/cameras/{id}/push-segment`. The backend caches segments in memory (60 per camera by default, ~60s buffer) and serves them through the same-origin proxy at `GET /api/cameras/{id}/segment/{file}`. The rewritten playlist contains relative segment URLs, so the browser's Clerk JWT auth header is automatically attached. No S3, no presigned URLs, no third-party storage in the live path. A global byte ceiling (`SEGMENT_CACHE_MAX_TOTAL_BYTES`, default 2 GiB) bounds total cache size across all cameras and evicts oldest-first when exceeded.
112
113
113
-
**Authentication:** Clerk handles user sign-up, login, and organization management. The backend validates JWT tokens (V1 and V2 permission formats) and extracts organization-scoped permissions. CloudNodes authenticate with API keys (SHA-256 hashed in the database) passed via `X-Node-API-Key`. MCP clients authenticate with `Authorization: Bearer osc_...` keys (also hashed).
114
+
**Authentication:** Clerk handles user sign-up, login, and organization management. The backend validates JWT tokens (V1 and V2 permission formats) and extracts organization-scoped permissions. CloudNodes authenticate with API keys (SHA-256 hashed in the database) passed via `X-Node-API-Key`. MCP clients authenticate with `Authorization: Bearer osc_...` keys (also hashed). Home Assistant authenticates with `osi_...` integration keys (same `mcp_api_keys` table, distinguished by a `kind` column; available on all plans).
114
115
115
116
**Storage:** Live segments live in the backend's in-memory cache; they expire automatically once `SEGMENT_CACHE_MAX_PER_CAMERA` is exceeded. Recordings and snapshots live on the CloudNode itself. **SQLite** is the production database (single-machine deploy on a Fly volume at `/data`); `DATABASE_URL` defaults to `sqlite:///./opensentry.db` for local dev. Incident snapshots and clips are stored inline on `IncidentEvidence.data` (LargeBinary) — evidence travels with the incident.
0 commit comments