π· CAMERA SYSTEM β canonical reference (don't re-discover this)
Last verified: 2026-06-25 (camera/infra session). LIVING doc. Evidence discipline: β = verified this session; π‘ = per Brain/prior, spot-check recommended; β οΈ = unverified/flag.
One-line truth (UPDATED 2026-07-01): cameras = Hikvision NVR β ops-apinvrFetch()DIRECT (digest auth, PC-FREE) β/camera/snapshotβ portal. Rewired to the NVR-direct path (deployeda405e728; all 4 cams verified HTTP 200 + JPEG straight from the NVR β no PC). go2rtc-on-PC is now only a FALLBACK and can be stopped. β οΈ NVR ports 8500/8501 are internet-exposed β Step 2 = cloudflared tunnel to the NVR from an always-on box, then close the eero forwards (raw Hikvision ports are actively exploited). Step-2 runbook βdocs/CAMERA_STEP2_TUNNEL_RUNBOOK.md(host = a ~$50 always-on LAN box, NOT the PC; exact steps; written 2026-07-01).
CAMERA MAP β (verified by on-screen OSD labels + cameras.html + Brain)
| cam=N | go2rtc RTSP channel | Location | Verified |
|---|---|---|---|
| cam1 | 102 | Backyard (OSD "REAR DOOR") | β read the frame |
| cam2 | 202 | Daughter's Room | π‘ Brain β intentionally HIDDEN from all portal views (privacy) |
| cam3 | 302 | Front Door | π‘ frame grabbed, label per Brain |
| cam4 | 402 | EMPTY connection β no camera (dropped); always 502 | β consistently dead |
| cam5 | 502 | Living / Dining | π‘ Brain |
| cam6 | 602 | Front Driveway | π‘ frame grabbed |
| cam7 | 702 | Kitchen (OSD "KITCHEN") | β read the frame |
| cam8 | 802 | Playroom (OSD "PLAYROOM") | β read the frame |
NOT a mislabel (Sam asked re cam7/cam8 2026-06-25): cam7=Kitchen, cam8=Playroom is CORRECT at the source AND in cameras.html. Source of truth for labels = outputs/cameras.html:90-92 ("Labels confirmed by Sam 2026-06-02"). cam=N maps to NVR channel N02 (sub-stream). |
ARCHITECTURE (A) β FALLBACK ONLY (as of 2026-07-01): go2rtc RTSP bridge (PC-tied)
Chain: cameras.html <img> β ops-api /camera/snapshot?cam=N (referer-gated) β Worker fetches https://cam.hookstreetservices.com/api/frame.jpeg?src=camN with headers CF-Access-Client-Id: <CAM_CF_ID> + CF-Access-Client-Secret: <CAM_CF_SECRET> (names only, never values) β cloudflared tunnel treitel-cameras β go2rtc on Sam's PC (localhost:1984) β NVR RTSP rtsp://admin:<NVR_PW>@192.168.4.47:54607/Streaming/Channels/N02 β JPEG (ffmpeg-transcoded). Code: ops-api/src/index.ts ~L630-668.
Why this exists: the NVR's direct HTTP/ISAPI web service stopped responding (see Architecture B / the security hardening), so we go around it via RTSP.
go2rtc setup on the PC (everything, exact)
- Install dir:
C:\Users\ztrei\go2rtc\βgo2rtc.exe(v1.9.14) +ffmpeg.exe(~143MB, needed for H.265βJPEG) +go2rtc.yaml. - Config
C:\Users\ztrei\go2rtc\go2rtc.yaml(creds redacted):
yaml streams: cam1: rtsp://admin:<NVR_PW>@192.168.4.47:54607/Streaming/Channels/102 cam2..cam8: ...Channels/202 .. 802 # cam=N -> channel N02 (sub-stream, lighter/H.264) api: listen: ":1984" username: "hsops" # basic auth; but go2rtc TRUSTS localhost so the tunnel password: "<GO2RTC_PW>" # (which connects from localhost) is NOT gated by this. ffmpeg: bin: "C:/Users/ztrei/go2rtc/ffmpeg.exe" # absolute -> cwd-independent - Ports: go2rtc API/web
:1984, RTSP:8554, WebRTC:8555(all local). - Start manually:
cd C:\Users\ztrei\go2rtc && go2rtc.exe -config go2rtc.yaml - Auto-start (INSTALLED 2026-06-25 β
):
C:\Users\ztrei\go2rtc\start-go2rtc.bat(doescd /d C:\Users\ztrei\go2rtc && start "" /min go2rtc.exe -config C:\Users\ztrei\go2rtc\go2rtc.yaml), copied into the Startup folderC:\Users\ztrei\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup\start-go2rtc.batβ relaunches go2rtc on every login. (Aschtasks ... /sc onlogontask needs admin; the Startup-folder copy does NOT β that's why we used it.) - Restart:
taskkill //F //IM go2rtc.exethen run the bat (or it relaunches at next login). - Verify locally:
curl http://localhost:1984/api/frame.jpeg?src=cam1βffd8...JPEG = healthy.http://localhost:1984/api/streams= producer states.
Cloudflare side
- Tunnel
treitel-cameras(id16a75a7b-5ee7-4715-8bf5-b6ae746eb8da), DNScam.hookstreetservices.comCNAMEβtunnel (proxied). Ingress βhttp://localhost:1984(go2rtc). Editable via CF API/accounts/<acct>/cfd_tunnel/<id>/configurations. The cloudflared Windows service runs the connector (dials out; no inbound port). - CF Access app "Treitel Cameras" (id
ab415497-863c-465c-ad68-16ada31d3d22) gatescam.hookstreetservices.comto Sam's login + a non-identity service-token policy for the ops-api Worker. - Service token "ops-api-camera-proxy" β
CAM_CF_ID(...access) +CAM_CF_SECRET(Worker secrets, names only). Rotate: create a new service token via CF API β add to the cam app's policy β set the two Worker secrets (CF-API way, see secret rules) β delete the old token.
ARCHITECTURE (B) β β CURRENT / LIVE (rewired 2026-07-01): NVR-direct, PC-FREE
Was: Worker nvrFetch() β http://d6468120.eero.online:8500/ISAPI/Streaming/channels/<ch>/picture (eero DDNS + eero port-forward β NVR, MD5 digest auth, NVR_HOST/NVR_USER/NVR_PASS). Verified working June 2 (commit 88c1454, "cam1 returns 34KB JPEG"). PC-FREE β the NVR served snapshots itself; nothing ran on the PC.
β οΈ CORRECTED 2026-07-01 (verified live from Sam's eero app β supersedes the old "closed June 1" claim below): the eero port-forwards are STILL OPEN + ENABLED β the NVR 192.168.4.47 has 8500 AND 8501 forwarded to the internet (Reservations & port forwards β "Hangzhou Hikvision Digital"). DDNS d6468120.eero.online β 74.101.240.91 is live (Dynamic DNS ON). So the NVR is currently internet-reachable β nvrFetch() (Architecture B, PC-FREE) works RIGHT NOW; a live GET to :8500 returned 200. The NVR is the ONLY forwarded device (iPod/laptop/iPads are LAN-only reservations). This is a real security exposure (internet-facing Hikvision = actively exploited) β the plan is: (1) rewire ops-api to nvrFetch() for PC-free cameras now, (2) stand up a cloudflared tunnel to the NVR from an always-on HOME box, (3) Sam disables the 8500/8501 eero forwards to close the hole. NEVER leave raw ports open long-term.
~~OLD (WRONG) note, kept for history:~~ "Why it broke: Sam's 2026-06-01 hardening moved the NVR port off 8500 + closed the eero 8500 forward β 8500 empty / 443 refused." β the eero shows the forward was never actually removed; 8500 is open. (The FABRICATED "firmware update killed it" citation in
ops-api/src/index.ts ~L642is also false β flag for correction.)
Restore (a SECURITY decision, Sam + camera/infra own it): thenvrFetch()helper is STILL inops-api/src/index.ts(only the call site was rewired). To go PC-free: revert/camera/snapshottonvrFetch()+ make the NVR HTTP reachable again. Secure form = a cloudflared tunnel β the NVR's HTTPS on an always-on box (NOT reopening raw 8500 to the internet). Reopening 8500 = the exposure the audit flagged. Deploy alone does NOT light cameras β the secret/route half ships, but the architecture choice is the actual restore.
SECRET / VERSION ARCHITECTURE + SAFE-RESTORE RULES (π΄ critical)
- ops-api secrets (30) are VERSION-EMBEDDED, not script-level.
wrangler secret listshows 0 even when all 30 are present β DO NOT trust it; the reliable check is a live secret-backed endpoint (e.g./camera/snapshotor/plaid/balances). - NEVER
wrangler secret putor a barewrangler deployon ops-api β strands all 30 secrets (happened twice 6/24-25; cameras + Plaid + comms all died). - Safe restore = CF-API rollback/roll-forward to the max-secret version (e.g. live =
ad740523, 30 secrets incl CAM_CF; earlier good =bbaba3ee). The Brain/#042 session owns this. - The masterβops-api build-clobber (every push rebuilt the wrong root
wrangler.tomlonto ops-api β stranded secrets) is FIXED 2026-06-25 (build path-scoped:root_directory:ops-api,path_includes:ops-api/*). Committing docs/skills/portal to master is now safe; onlyops-api/*code edits are sensitive.
DIAGNOSTIC DECISION TREE (502 / "no signal")
- ALL cams 502 + a secret-backed endpoint also fails β secrets stranded (clobber/bad deploy). Fix: Brain rolls ops-api to the max-secret version.
- ALL cams 502 + other endpoints WORK β wrong/regressed ops-api version (route missing). Fix: roll forward to the full version (
ad740523). - SOME cams work, one is 502 / "no signal" β THAT camera's source feed (go2rtc/RTSP/physical), NOT ops-api. Check:
curl localhost:1984/api/frame.jpeg?src=camN. If local works but portal 502s = tunnel/ops-api; if local also fails = the physical camera/NVR channel (power-cycle/reseat when home). cam4 is permanently dead (empty connection). - Brief "no signal" that self-recovers β transient RTSP drop; go2rtc auto-reconnects. Refresh. Only worry if a camera flaps persistently (then physical).
5-POINT ACCEPTANCE TEST (not "done" until all pass)
- PC awake. 2. go2rtc + cloudflared running (
tasklist). 3. ops-api has CAM_CF (live/camera/snapshottest, NOTsecret list). 4. Worker β tunnel returns JPEGs (7/8; cam4 dead). 5. Feed loads on Sam's phone off Wi-Fi (cellular).
DURABILITY ROADMAP
- β go2rtc auto-start (Startup folder) β survives reboot while PC is on.
- β¬ Always-on home box (~$150 mini-PC/Pi) running go2rtc + cloudflared β cameras survive the PC being OFF (a cloud VM can't β it can't reach the home LAN). THIS is the durable end state.
- β¬ Optional: revert to the PC-free Architecture B (security tradeoff) to drop go2rtc entirely.
CURRENT STATE (2026-06-25)
Cameras LIVE via go2rtc (7/8; cam4 dead). ops-api live ad740523 (CAM_CF present). go2rtc + cloudflared running + auto-start installed. cam1/Backyard occasionally shows a transient "no signal" that self-recovers (6/6 good on last test). Portal labels correct. Off-Wi-Fi confirmed by Sam.