Camera and NVR Streaming/Management
  • Python 54.3%
  • JavaScript 43.7%
  • Shell 1.2%
  • Makefile 0.2%
  • HTML 0.2%
  • Other 0.4%
Find a file
elirtf e37e5ad180 Merge decide/repo-layout into dev — ticket 12 layout refactor
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-07 15:51:10 -05:00
.claude/skills refactor(layout): move compose to docker/, proxy configs to proxy/ 2026-08-07 15:50:15 -05:00
.cursor/rules feat(recordings): auto-calibration, NAS storage support, settings UI overhaul 2026-06-04 08:55:23 -05:00
.scratch refactor(layout): move compose to docker/, proxy configs to proxy/ 2026-08-07 15:50:15 -05:00
app refactor(layout): move compose to docker/, proxy configs to proxy/ 2026-08-07 15:50:15 -05:00
deploy feat(detection): replace MobileNet-SSD with YOLO via OpenVINO 2026-06-30 15:40:37 -05:00
docker refactor(layout): move compose to docker/, proxy configs to proxy/ 2026-08-07 15:50:15 -05:00
docs refactor(layout): move compose to docker/, proxy configs to proxy/ 2026-08-07 15:50:15 -05:00
frontend refactor(layout): move compose to docker/, proxy configs to proxy/ 2026-08-07 15:50:15 -05:00
go2rtc /api/cameras/<name>/stats 1 2026-04-01 16:52:01 -05:00
mobile Update README.md 2026-03-30 14:18:51 -05:00
monitoring refactor(layout): move compose to docker/, proxy configs to proxy/ 2026-08-07 15:50:15 -05:00
proxy refactor(layout): move compose to docker/, proxy configs to proxy/ 2026-08-07 15:50:15 -05:00
scripts refactor(layout): move compose to docker/, proxy configs to proxy/ 2026-08-07 15:50:15 -05:00
tests refactor(layout): move compose to docker/, proxy configs to proxy/ 2026-08-07 15:50:15 -05:00
.dockerignore refactor(layout): move compose to docker/, proxy configs to proxy/ 2026-08-07 15:50:15 -05:00
.editorconfig chore(dev): add ruff, prettier, pre-commit, editorconfig, and Makefile lint/format targets 2026-05-15 14:31:23 -05:00
.env.example refactor(layout): move compose to docker/, proxy configs to proxy/ 2026-08-07 15:50:15 -05:00
.gitattributes for windows compatibility 2026-04-08 16:01:17 -05:00
.gitignore refactor(layout): move compose to docker/, proxy configs to proxy/ 2026-08-07 15:50:15 -05:00
.pre-commit-config.yaml chore(dev): add ruff, prettier, pre-commit, editorconfig, and Makefile lint/format targets 2026-05-15 14:31:23 -05:00
AGENTS.md chore(project): agent-skills config (issue tracker, domain, triage) + wayfinder map 2026-07-27 13:03:44 -05:00
docker-entrypoint-opus.sh perf(recorder): stage segment cache on local disk; surface drain failures 2026-06-29 13:19:42 -05:00
Dockerfile feat: CPU object detection, motion-gated recording, production deployment improvements 2026-06-17 10:18:54 -05:00
LICENSE Update LICENSE 2026-03-04 16:00:53 -06:00
Makefile refactor(layout): move compose to docker/, proxy configs to proxy/ 2026-08-07 15:50:15 -05:00
pyproject.toml feat(recordings): auto-calibration, NAS storage support, settings UI overhaul 2026-06-04 08:55:23 -05:00
README.md refactor(layout): move compose to docker/, proxy configs to proxy/ 2026-08-07 15:50:15 -05:00
requirements.txt feat(security): encrypt camera RTSP credentials at rest; pin dependencies 2026-08-04 11:35:22 -05:00
run.py 1. CRITICAL — Flask / nginx 502 (connection refused) 2026-04-15 08:50:42 -05:00

Opus Logo

Opus NVR

A lightweight, self-hosted IP camera recorder and viewer. For new installs, cameras send RTSP directly to Opus (live + continuous recording + timeline playback). A vendor NVR is optional—the UI still supports importing channels from an existing recorder for migration only.

Deploy with Docker Compose on Linux (Ubuntu Server is the documented standard).

License: MIT Docker Python Status Repo Size


Features

Live Viewing

Streaming

Devices & Configuration

Sites (legacy NVR import), camera list, and Configuration (system info, Settings / go2rtc + recording options, diagnostics, per-site stream table with RTSP edit).

Authentication & Access Control


Tech Stack

Layer Technology
Backend Flask
Authentication Flask-Login
Database SQLite via Peewee (migrations in app/migrations/)
Stream Server go2rtc
Reverse Proxy nginx
Frontend React + Tailwind CSS
Containerization Docker + Docker Compose

Getting Started

Prerequisites

  • Docker Desktop (Windows/macOS) or Docker Engine + Compose (Linux)
  • Git

Setup

# 1. Clone the repo
git clone https://github.com/elirtf/opus.git
cd opus

# 2. Create your environment file
# Edit .env and set SECRET_KEY, and optionally GO2RTC_URL
      # GO2RTC_URL=http://go2rtc:1984 (Default)
      # GO2RTC_CONFIG_PATH=/config/go2rtc.yaml (Default — Opus writes this from Configuration → Settings)
      # GO2RTC_RTSP_URL=rtsp://go2rtc:8554 (Default)
      # GO2RTC_ALLOW_ARBITRARY_EXEC=false (Optional — overrides UI “allow exec sources” when set)
      # SECRET_KEY=secret-key
      # Optional: comma-separated browser origins for split-host UIs (enables flask-cors)
      # CORS_ORIGINS=https://app.example.com,http://localhost:5173

# 3. Build and start
docker compose up --build

The app will be available at http://localhost.

You do not need Node.js or npm on your PC for this — the Docker build installs frontend dependencies and produces the UI inside the image. Run npm install / npm run dev in frontend only when you are developing or testing the web UI yourself (especially mobile: layout, PWA, live view on a phone). See docs/DEV_WORKFLOW.md for that optional workflow.

Remote access

Off-site / phones: use a mesh VPN (e.g. Tailscale) so devices reach Opus privately — see docs/remote-viewing.md. Before release, run docs/MOBILE_QA_v1.md with VPN on and cellular data.

Operational alerts

Background checks can POST JSON to ALERT_WEBHOOK_URL and/or send email via SMTP (ALERT_SMTP_HOST, ALERT_EMAIL_FROM, ALERT_EMAIL_TO, optional ALERT_SMTP_USER / ALERT_SMTP_PASSWORD). Email uses the same payloads as the webhook, so lock-screen notifications come from the mail app users already have. See commented variables in docker/docker-compose.yml and the module docstring in app/ops_alerts.py.

First-time Setup

On a fresh database (no user accounts), the UI sends you to /setup to create the administrator username and password. Passwords are stored with bcrypt. There are no baked-in default credentials.

If SECRET_KEY is not set in .env, Opus writes a random key to instance/.flask_secret_key (next to the SQLite file) so the API container can start without manual env editing.

If you see the login page again after an update, the session usually expired — sign in with the same credentials you created at setup.


Stream Architecture

IP Camera (RTSP)
      │
      ├──────────────────────────────┐
      ▼                              ▼
 go2rtc (:1984)                 Recorder + FFmpeg
 (live / MSE in browser)         (MP4 segments on disk)
      │                              │
      └────────── nginx (:80) ───────┘
                      │
                 Opus API + React UI

Recommended path: one RTSP URL per camera (main for recording; optional substream for live tiles). Migration path: import channels from an existing NVR under Devices → Sites & migration.

Motion and event-based recording

Opus can record continuously (full timeline retention) or in Events mode (motion-triggered clips). For Events mode:

  • Run the processor service from Docker Compose (app.processing_service). By default it samples the sub stream for motion when one exists (lower CPU); clips are still captured from main (-c:v copy). Set MOTION_RTSP_MODE=main on the processor to force full-res motion sampling. Clips live under RECORDINGS_DIR/clips/. Use the Recordings → Events tab (playback). Live view also prefers sub when configured.
  • Choose the mode per camera under Recordings → Settings → Camera Recording: Off, Continuous, or Events (motion). You can also set recording_policy to events_only or continuous via PATCH /api/cameras/<id>.
  • By default, Events mode does not run 24/7 segment recording (no always-on FFmpeg writer for those cameras), so the Playback timeline stays empty for them — footage lives under Events as motion clips. Opus does not read camera/NVR “motion only” flags; it decides motion in software using the processor. If you want a rolling segment buffer on disk for pre-roll (like a traditional NVR), set EVENTS_ONLY_RECORD_SEGMENTS=1 (or true / yes / on) on the recorder service — any other value leaves Events as clip-only (see docker/docker-compose.yml and docs/hardware-sizing.md).
  • Tune behavior with environment variables on the processor (and shared retention settings): see docs/hardware-sizing.md for PROCESSING_POLL_SECONDS, CLIP_SECONDS, MOTION_COOLDOWN_SECONDS, MOTION_RTSP_MODE, EVENTS_ONLY_BUFFER_HOURS, CLIP_RETENTION_DAYS, and related notes.
  • If you record through the go2rtc RTSP relay (GO2RTC_RTSP_URL), use the same URL on both the recorder and processor services so segments, motion sampling, and clips refer to the same paths (details in docker/docker-compose.yml).
  • Clip timing: Under Recordings → Settings, configure core capture length, optional post-roll (extra seconds after the trigger), pre-roll (up to 15s from the latest completed segment file when segment files exist), poll interval, and cooldown. The same values can be set via environment variables on the processor service (CLIP_SECONDS, CLIP_PRE_SECONDS, CLIP_POST_SECONDS, etc.); DB settings override env when set in the UI. True “seconds before motion” without any buffer is not possible from live RTSP alone — pre-roll uses recorded segments when available.

Documentation

Doc Topic
docs/certified-cameras.md Certified list + regression checklist
docs/hardware-sizing.md Storage, retention, processor/recorder env notes
docs/nvr-replacement-lab.md Migration / lab validation
docs/DEV_WORKFLOW.md Local dev: Compose vs split loop, Makefile
docs/remote-viewing.md Remote viewing (VPN-first) + optional public URL
docs/MOBILE_QA_v1.md Mobile QA before release
docs/PRODUCT_SCOPE_V1.md v1.0 scope summary
mobile/README.md Optional App Store / Play wrapper

Development

Step-by-step commands (PowerShell, env vars, Makefile on WSL): docs/DEV_WORKFLOW.md.

After git pull: run docker compose up --build -d so Opus rebuilds from your repo (docker compose pull alone only updates pre-built images like nginx/go2rtc, not your app code). The opus container syncs the fresh React build into the static_files volume on every start so the UI updates without pruning volumes. Do not rely on docker prune for routine updates.

Typical fast loop (optional — for developing the frontend, including mobile; not required if you only use full Docker): run go2rtc with Docker (docker compose up go2rtc), run Flask (python run.py), and run the Vite dev server (cd frontend && npm install && npm run dev). Set GO2RTC_URL=http://127.0.0.1:1984 for local Flask so it talks to the published go2rtc port. The frontend proxies /api to localhost:5000 and /go2rtc to go2rtc on localhost:1984 (see frontend/vite.config.js). Optional: copy docker/compose.override.yml.example to compose.override.yml for local compose tweaks (file is gitignored).

To run the backend alone:

# Create a virtual environment
python -m venv .venv
.venv\Scripts\activate       # Windows
source .venv/bin/activate    # macOS/Linux

# Install dependencies
pip install -r requirements.txt

# Set env vars
set SECRET_KEY=dev            # Windows
export SECRET_KEY=dev         # macOS/Linux

# Run
python run.py

You'll still need go2rtc running separately (or skip it — the app works without it, streams just won't load).

Quick smoke check: log in, open dashboard live tile, run Discovery (subnet scan uses background job + polling), open Recordings and confirm engine status loads when the recorder service and RECORDER_INTERNAL_STATUS_URL are set.


Contributing

  1. Fork the repo and create a feature branch (git checkout -b feature/my-thing)
  2. Make your changes, test with docker compose up --build
  3. Open a pull request with a clear description of what changed and why

Please keep PRs focused — one feature or fix per PR makes review much easier.


License

See LICENSE file.