Skip to main content

Repository layout

Node modules

The node is a TypeScript service (run with tsx, no build step) that wires these modules together:

Data flow for one event

  1. The motion detector sees 2 consecutive changed frames on a camera’s substream and emits motionStart.
  2. 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 motion to person/vehicle/animal/package. A second pass runs 10 s in.
  3. 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.
  4. 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 + secret for 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 :8080 is unauthenticated by design and meant for your LAN.
Edge functions: 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 modelpackage is currently approximated from bag-like COCO classes
  • Familiar faces (listed on Pro) — coming
  • Detection thresholds and motion tuning as configuration rather than code constants
The full picture lives in the issue tracker.