Detect a disconnected or read-only recordings drive and surface it without stopping recording or live view: - storage.go: probe path existence, mount point, writability and free space every 30s; capture mount expectation at startup so a clean unmount (writes falling through to root fs) is also caught - GET /api/storage endpoint; storage fields in /api/status - red UI banner while storage is unavailable - log ALERT on loss, OK on recovery |
||
|---|---|---|
| public | ||
| .gitignore | ||
| api.go | ||
| auth.go | ||
| CHANGELOG.md | ||
| cleaner.go | ||
| config.go | ||
| config.json.sample | ||
| deploy_nvr.sh | ||
| go.mod | ||
| go.sum | ||
| go2rtc.go | ||
| LICENSE | ||
| main.go | ||
| onvif.go | ||
| README.md | ||
| recorder.go | ||
| remove.sh | ||
| server.go | ||
| snapshot.go | ||
| storage.go | ||
| VERSION | ||
NextNVR
A simple, single-binary IP camera recorder. Think of it as a dashcam for your property.
Why NextNVR?
The open-source NVR landscape is rich with incredible projects — Frigate, Shinobi, ZoneMinder, Blue Iris alternatives — backed by passionate communities. They're powerful. They're feature-packed. But they've also become complex. Docker containers, AI pipelines, multi-service architectures, cloud integrations — for many users, it's too much.
NextNVR is different. It does one thing: continuously record your IP cameras to disk and let you review the footage. No AI, no cloud, no Docker — just a single Go binary you can run on a Raspberry Pi 5, an old thin client, or a VM. Deploy in 30 seconds — download, run, done.
This project was built with the help of AI — designed through conversation, refined through iteration, and shaped by the simple principle that surveillance software should be boring. It should just work.
Quick Install
curl -sSLO https://git.lohmar.co.uk/cclohmar/NextNVR/raw/branch/main/deploy_nvr.sh
chmod +x deploy_nvr.sh
./deploy_nvr.sh
The script asks where to store recordings, then installs everything. Press Enter for the default (~/nvr_data) or type your own path.
To update an existing installation — run the same script again. It stops the service, pulls the latest code, rebuilds, and restarts.
To pre-set the storage path:
STORAGE_PATH=/mnt/recordings ./deploy_nvr.sh
Supported Systems
| Distro | Package Manager | Architecture |
|---|---|---|
| Debian 12+ / Ubuntu 22.04+ | apt |
amd64, arm64 |
| Fedora 40+ / RHEL 9+ | dnf / yum |
amd64, arm64 |
| Arch Linux | pacman |
amd64, arm64 |
| Alpine Linux | apk |
amd64, arm64 |
| Raspberry Pi 5 | any above | arm64 |
What It Does
| Feature | Detail |
|---|---|
| 📹 Recording | Up to 8 cameras simultaneously, 5-minute MP4 clips, zero transcoding |
| 💾 Storage | Set any path — local disk, USB drive, or network mount |
| 🖥️ Live Wall | 4×2 grid with auto-refreshing snapshots. Click for full live stream |
| 🔍 Playback | Browse recordings by camera and date with snapshot thumbnails |
| ⚙️ Setup Wizard | ONVIF network scan + camera naming. Add cameras manually too. |
| 🔐 Two-Port Auth | :8080 with optional master login, :8090 always-open viewer for LAN |
| 🔒 100% Local | Everything runs on your hardware. No cloud, no subscriptions, no telemetry |
How Live Streaming Works
NextNVR doesn't transcode video itself. It delegates all live-stream conversion to go2rtc, a separate binary it launches as a child process. The camera does the encoding — NextNVR and go2rtc pass the stream through unchanged.
IP Camera (RTSP)
│ sub stream: /Streaming/Channels/102 (live view)
│ main stream: /Streaming/Channels/101 (recording)
▼
go2rtc (child process, listens on :1984)
│ holds persistent RTSP connections, re-serves as WebRTC / MSE / MJPEG
▼
NextNVR (proxies /go2rtc/ through its own port for a single browser origin)
Two kinds of "live"
| View | Mechanism | Notes |
|---|---|---|
| Live Wall grid | 1 fps JPEG snapshot polling | Not a video stream. The snapshot engine grabs a frame every second and writes latest.jpg; the browser refreshes it every 3 seconds. Cheap — ideal for an overview. |
| Focus view (click a tile) | Real stream via go2rtc | Opens go2rtc's stream.html player, which negotiates WebRTC → MSE → MJPEG. Uses the camera substream for low bandwidth. |
Codec & resolution
- Zero transcoding. The recorder uses
-c copy(stream-copy); go2rtc passes the codec through unchanged. The browser receives exactly what the camera encodes. - Codec = your camera's codec. Set cameras to H.264 — WebRTC support for H.265/HEVC is poor, and NextNVR will not re-encode it.
- Live view uses the substream (
{id}_sub); the main stream ({id}_main) is recorded to disk but is not shown in the live view. - Ports: go2rtc runs on
:1984; NextNVR reverse-proxies/go2rtc/through its own port so a single origin serves the browser (no mixed-content issues behind HTTPS).
Where to Store Recordings
NextNVR saves video clips and snapshots to a directory you choose. By default this is ~/nvr_data. You can change it in Settings → Storage path or by editing config.json.
Local disk
# Use the system drive (default)
Storage path: /home/pi/nvr_data
# Use a second internal drive
sudo mkdir /data/nvr
sudo chown $USER:$USER /data/nvr
# Then set: /data/nvr
USB drive
# Plug in the drive, find it:
lsblk
# Mount it:
sudo mkdir /mnt/usb-drive
sudo mount /dev/sdb1 /mnt/usb-drive
# Make it permanent (/etc/fstab):
# /dev/sdb1 /mnt/usb-drive ext4 defaults 0 2
# Then set: /mnt/usb-drive/nvr
Network share (NAS / NFS)
# Install NFS client:
sudo apt install nfs-common
# Mount the share:
sudo mkdir /mnt/nas
sudo mount 192.168.1.100:/volume1/nvr /mnt/nas
# Make it permanent (/etc/fstab):
# 192.168.1.100:/volume1/nvr /mnt/nas nfs defaults 0 0
# Then set: /mnt/nas
Authentication
NextNVR runs two HTTP servers:
| Port | Audience | Auth | Access |
|---|---|---|---|
8080 |
You (admin) | Optional master login | Live Wall, Playback, Settings |
8090 |
Guests / LAN | None | Live Wall only |
Auth is disabled by default. To enable it, open Settings → 🔐 Authentication card, set a master username and password, check "Require login", and save. Port 8090 stays open regardless — ideal for a QR code at a guesthouse: "Watch what we watch."
To reset the master password, edit /opt/nextnvr/config.json and set "auth": {"enabled": false}, then restart.
Removing
curl -sSLO https://git.lohmar.co.uk/cclohmar/NextNVR/raw/branch/main/remove.sh
chmod +x remove.sh
./remove.sh
Stops the service, removes /opt/nextnvr/, cleans up systemd. Recordings on disk are untouched.
Remote Access (Optional)
- Dynamic DNS — use DuckDNS, No-IP, or Cloudflare for a domain name
- Port forwarding — forward port
8080to the NextNVR server (and8090if you want LAN guests to have open access) - For production, put NextNVR behind a reverse proxy (Zoraxy, Nginx) with HTTPS
Roadmap
| Feature | Status |
|---|---|
Continuous recording (-c copy, zero transcoding) |
✅ Done |
| ONVIF auto-discovery + web setup wizard | ✅ Done |
| Live wall with snapshot grid + focus view | ✅ Done |
| Playback browser with date presets | ✅ Done |
| 7-day retention with auto-cleanup | ✅ Done |
| Two-port authentication (master + viewer) | ✅ Done |
| NAS / Network storage (point recordings to any mounted path — NFS, SMB, USB) | ✅ Done |
| Camera-triggered recording (ONVIF motion/person detection events via API) | 🔜 Planned |
| Multi-platform builds (Raspberry Pi, ARM SBCs) | 🔜 Planned |
Not Planned
- ❌ Cloud connectivity of any kind
- ❌ AI/ML object detection (use your camera's built-in detection)
- ❌ Mobile apps (the web UI is responsive and PWA-ready)
Server Commands
sudo systemctl status nextnvr # is it running?
sudo systemctl restart nextnvr # restart
sudo journalctl -u nextnvr -f # watch logs
Config file: /opt/nextnvr/config.json
Recordings: /mnt/recordings/
License
MIT — see LICENSE
Built with ❤️ and AI in Mindelo, Portugal. 🇵🇹