# NVR Shield — Architecture ## System Overview ``` ┌─────────────────────────────────────────────────────────┐ │ CUSTOMER SITE │ │ │ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │ │ NVR #1 │ │ NVR #2 │ │ NVR #3 │ (Reolink) │ │ │ 192.168.x│ │ 192.168.x│ │ 192.168.x│ │ │ └────┬─────┘ └────┬─────┘ └────┬─────┘ │ │ │ │ │ │ │ └──────────────┼──────────────┘ │ │ │ HTTP API (port 80/443) │ │ ┌───────┴────────┐ │ │ │ Pi Zero 2W │ │ │ │ NVR Shield │ │ │ │ Agent │ │ │ └───────┬────────┘ │ │ │ Tailscale (WireGuard) │ └──────────────────────┼───────────────────────────────────┘ │ INTERNET │ │ ┌──────────────────────┼───────────────────────────────────┐ │ ITPP CORE (netcup) │ │ │ │ │ ┌───────┴────────┐ │ │ │ NVR Shield │ │ │ │ Dashboard │ │ │ │ API (:8090) │ │ │ └───────┬────────┘ │ │ │ │ │ ┌────────────┼────────────┐ │ │ │ │ │ │ │ ┌────┴────┐ ┌────┴────┐ ┌───┴──────┐ │ │ │ Wasabi │ │ Alerting │ │ Monthly │ │ │ │ S3 │ │ (Email/ │ │ Reports │ │ │ │ Backups │ │ SMS) │ │ Generator│ │ │ └─────────┘ └─────────┘ └──────────┘ │ └───────────────────────────────────────────────────────────┘ ``` ## Components ### 1. Pi Zero 2W Agent (`agent.py`) Runs on Raspberry Pi Zero 2W at customer site. Python 3. **Responsibilities:** - Poll each configured NVR every 60s via Reolink HTTP API (`reolink-aio`) - Collect: camera online status, HDD health, storage %, firmware version, uptime - Push metrics to Core heartbeat endpoint - Dump full NVR config to Wasabi S3 nightly - Capture motion/AI-triggered clips and upload to S3 (Pro/Enterprise tiers) - Accept remote config and update commands from Core - Run custom AI filtering rules (Enterprise tier) **Config schema (`config.yaml`):** ```yaml site_id: "cust-shortname" heartbeat_url: "https://core.itpropartner.com/nvr-shield/heartbeat" s3_bucket: "nvr-shield-customer-backups" s3_prefix: "cust-shortname/" poll_interval: 60 # seconds config_backup_time: "03:00" # daily nvrs: - name: "Warehouse" ip: "192.168.1.10" port: 443 user: "admin" password: "${NVR_PASSWORD_WAREHOUSE}" - name: "Front Office" ip: "192.168.1.11" port: 443 user: "admin" password: "${NVR_PASSWORD_FRONTOFFICE}" ``` ### 2. Core Dashboard / API FastAPI backend on Core (:8090 path `/nvr-shield/`). **Endpoints:** | Method | Path | Purpose | |---|---|---| | POST | `/nvr-shield/heartbeat` | Agent pushes metrics (site_id, nvrs[], metrics) | | GET | `/nvr-shield/dashboard` | Admin dashboard — all sites, health, alerts | | GET | `/nvr-shield/site/{id}` | Per-site detail (cameras, history, clips) | | GET | `/nvr-shield/report/{id}/{month}` | Monthly report PDF/HTML | | POST | `/nvr-shield/agent/{id}/config` | Push updated config to agent | ### 3. Wasabi S3 Storage **Bucket:** `nvr-shield-customer-backups` **Structure:** ``` s3://nvr-shield-customer-backups/ {site_id}/ configs/ 2026-08-01-config.json 2026-08-02-config.json clips/ 2026-08-01/ 14-32-15-NVR-Warehouse-Cam3-motion.mp4 reports/ 2026-07-monthly-report.pdf ``` ### 4. Tailscale Network All Pi Zero 2W devices connect via Tailscale to ITPP's tailnet. Each device tagged `tag:nvr-shield`. Core communicates with agents over Tailscale IPs, not public internet. ## Data Flow 1. **Health polling (every 60s):** Agent → NVR HTTP API → metrics JSON → Core heartbeat endpoint 2. **Nightly config backup:** Agent → NVR HTTP API → config JSON → Wasabi S3 3. **Incident clips (Pro/Enterprise):** NVR motion/AI event → agent detects → clip upload to S3 → notification to Core 4. **Monthly report:** Core cron (1st of month) → aggregates metrics from DB + S3 → generates PDF → emails customer 5. **Alerts:** Core evaluates heartbeat gaps (>2 min = alert), disk thresholds (>90% = alert), camera offline ## Security - Pi filesystem is read-only (overlayfs) to prevent SD card corruption - SSH key-only, no password auth - NVR passwords stored in env vars, never in git - Tailscale WireGuard for all agent-Core communication - S3 credentials scoped to per-customer prefix only