- Python 64.8%
- JavaScript 32.7%
- Shell 1.3%
- HTML 0.5%
- Makefile 0.3%
- Other 0.4%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
The original design spec (docs/superpowers/specs/2026-09-08-admin-mfa-design.md) listed mandatory MFA enforcement as an explicit non-goal — opt-in, self-service. The implementation contradicted this by hard-gating every admin session behind 2FA enrollment, blocking all API calls and rendering a non-dismissible gate page right after account creation. This restores the opt-in intent: - OPUS_REQUIRE_ADMIN_MFA env flag (default: false) controls the enforcement mode - When false (default): admin can use the system without 2FA; a dismissible enrollment nudge appears once per session with a "Skip for now" confirmation - When true: current hard-gate behavior (403 on all routes except enrollment allowlist, no self-disable, no skip option) - /api/auth/me exposes require_admin_mfa so the frontend knows the policy - Admin self-disable (/mfa/disable) is allowed when MFA is optional - Frontend canDisable in TwoFactorSettings respects the flag - Typo fix: "Enrol" → "Enroll" in MfaSetup.jsx Tests: existing mandatory-mode tests now set the flag via fixture; 5 new tests cover the optional (default) mode — unenrolled admin can access require_auth and require_admin routes, go2rtc verify, /me reports the flag, and self-disable works. |
||
| .claude/skills | ||
| .github/workflows | ||
| .scratch | ||
| app | ||
| deploy | ||
| docker | ||
| docs | ||
| frontend | ||
| mobile | ||
| monitoring | ||
| proxy | ||
| scripts | ||
| tests | ||
| .dockerignore | ||
| .editorconfig | ||
| .env.example | ||
| .gitattributes | ||
| .gitignore | ||
| .gitleaks.toml | ||
| .pre-commit-config.yaml | ||
| AGENTS.md | ||
| docker-entrypoint-opus.sh | ||
| LICENSE | ||
| Makefile | ||
| pyproject.toml | ||
| README.md | ||
| requirements.txt | ||
| run.py | ||
| SECURITY.md | ||
Opus NVR
Watch and record your security cameras, on your own hardware.
Point your cameras at Opus and it records them around the clock. Open a browser for a live wall of cameras and a timeline you can scrub back through.
It runs on a machine you own. The video stays on your disk. No cloud account, no subscription.
- Live view — all your cameras at once, or one full size. Under a second behind real life.
- Recording — everything 24/7, or only when something moves. Set per camera.
- Search — Opus can tell a person from a car from an animal, so you can skip the wind in the trees.
- Users — give people access to some cameras and not others, and choose who can download footage (this stops casual saving, not a determined person).
- Alerts — email or a webhook when a disk fills up or a camera drops off.
Install
You need a Linux machine, Docker, Git and make.
git clone https://git.willowcrest.world/elirtf/opus.git
cd opus
cp .env.example .env
Open .env and fill in the two keys at the top:
openssl rand -hex 32 # SECRET_KEY
openssl rand -base64 32 | tr '+/' '-_' # OPUS_ENCRYPTION_KEY
Set
OPUS_ENCRYPTION_KEYonce and never change it. It encrypts your camera passwords. Change it and they can never be read again — you would re-enter every camera by hand. Back it up.
make up
Open http://localhost and create your admin account. There is no default password.
Next steps
Add cameras — Opus can scan your network for them, or you can paste in an address.
Back up — scripts/backup.sh saves your database and your encryption key. One
without the other is useless. How to run it.
Watch from elsewhere — use a private network like Tailscale. Don't expose Opus to the internet directly. Instructions.
Something broken? Usually a stale browser: press F12 → Application → Clear site data, then reload twice. Everything else.
Monitoring (optional)
Prometheus keeps 90 days of history; Grafana shows it as a dashboard — are the services up, which cameras dropped off, is motion detection keeping up, how full is the disk.
make up-monitoring
The first run adds three generated values to .env. Nothing to fill in by hand:
.env key |
What it is |
|---|---|
OPUS_METRICS_TOKEN |
Lets Prometheus read the app's metrics. Also copied to monitoring/metrics_token. |
GRAFANA_ADMIN_PASSWORD |
Grafana login, user admin. |
PROMETHEUS_PASSWORD |
Prometheus login, user opus. Its hash goes to monitoring/prometheus_web.yml. |
See them with grep -E 'GRAFANA|PROMETHEUS' .env.
| Address | |
|---|---|
| Grafana | http://<host>:3000/d/opus-nvr |
| Prometheus | http://<host>:9090 |
Only reachable from the Opus machine by default (<host> = 127.0.0.1). To open them to
your LAN or Tailscale, add MONITORING_BIND=0.0.0.0 to .env and run make up-monitoring
again. Both still ask for a login — but Docker skips the host firewall, so never
port-forward 3000 or 9090 on your router.
Changing the Grafana password: editing .env only works before Grafana's first start.
After that: docker exec opus-grafana grafana cli admin reset-admin-password <new>, then put
the same value in .env.
Page won't load? docker ps | grep -E 'grafana|prometheus' — if neither is listed, run
make up-monitoring. Details: Monitoring.
Documentation
| Doc | What it covers |
|---|---|
| Recording & storage | Where footage goes, how long it's kept, using a NAS |
| Hardware sizing | How much disk and CPU you need |
| Remote viewing | Watching from outside your network |
| Object detection | Telling people from cars from animals |
| Alerts & monitoring | Email/webhook alerts, self-healing, Prometheus/Grafana |
| Motion notifications | Telegram, Discord, Matrix setup |
| Migrating from an NVR | Moving off an existing recorder |
| Home Assistant | MQTT and smart-home integration |
| H.265 / HEVC | Browser codec limits for live view |
| Installing without Docker | Running directly on the machine |
| Caddy trial (experimental) | Opt-in nginx alternative, side by side on its own port |
| Development | Working on Opus itself |
| Release checklist | Camera + phone checks before a release |
| Project scope | What v1 covers |
| Security checklist | OWASP SAMM (SDLC) + WSTG (runtime testing) checklist tailored to Opus |
Contributing
Branch off dev, run make check and pytest -q, try it with make up, then open a pull
request. One change per request.
Anything touching live view, playback or detection needs someone to look at the screen — the tests cannot see a black tile.
Details: docs/DEV_WORKFLOW.md.
MIT licensed — see LICENSE.