Repository layout
Node modules
The node is a TypeScript service (run withtsx, no build step) that wires these modules together:
Data flow for one event
- The motion detector sees 2 consecutive changed frames on a camera’s substream and emits
motionStart. - The pipeline opens a candidate event and fetches a full-color snapshot from go2rtc; the detect worker runs YOLOX-tiny and may upgrade the event from
motiontoperson/vehicle/animal/package. A second pass runs 10 s in. - Motion ends after 10 s of quiet. The pipeline waits for post-roll footage to land on disk, cuts a clip (start −5 s to end +5 s) from the recorder’s segments with stream copy, and extracts a mid-clip thumbnail.
- The event is written to local SQLite and, if the node is paired, an upload job is enqueued. A serial uploader pushes the event row, then the clip and thumbnail via signed URLs, then the clip metadata row — idempotently, with retries and backoff. Offline, jobs wait.
Security model
- Machine identity, not user identity. The node generates a secret on first boot and registers it with an edge function, which creates a dedicated machine auth user. The node exchanges
nodeId + secretfor a short-lived node-scoped JWT (refreshed before expiry). Your user credentials never touch the node. - Row-level security. Postgres RLS restricts the node’s JWT to its own rows: its node record, its cameras, its events and clips. Users, in turn, can only see nodes they have claimed.
- Private realtime channels. Signaling runs on a
node:{nodeId}channel marked private — only the node and the claiming user’s sessions can join. It carries WHEP offers/answers, snapshot requests, and status requests. - Storage path scoping. Uploads go to node-scoped storage paths using signed upload URLs minted by an edge function; the node cannot write outside its own prefix. Snapshots are shared back as short-lived signed URLs.
- Credentials stay home. Camera RTSP URLs (which embed camera passwords) exist only in the node’s
config.json. The cloud receives a projection with URLs stripped. - No inbound surface. The node makes outbound connections only. The local API on
:8080is unauthenticated by design and meant for your LAN.
node-register (pairing codes), node-token (secret → JWT exchange), node-claim (tie node to account), sign-upload (signed storage URLs), turn-credentials (STUN list always; short-lived Cloudflare TURN credentials when configured).
What is roadmap, not shipped
Nightjar is early. Implemented and real: everything described above. Planned or partial:- Nest camera bridge via Google’s SDM API — issue #7
- ONVIF discovery — cameras are added by RTSP URL today
- Dedicated package-detection model —
packageis currently approximated from bag-like COCO classes - Familiar faces (listed on Pro) — coming
- Detection thresholds and motion tuning as configuration rather than code constants