A minimal CCTV/NVR system in ~92KB. Records RTSP streams from IP cameras into 15-minute chunks, with a web UI to browse and play recordings, watch live streams, and manage storage.
Built as a replacement for Shinobi — no Docker, no Node.js, no database. Just bash, ffmpeg, and python3.
- Records up to N cameras simultaneously as boundary-aligned 15-min MP4 chunks
- Web UI — browse recordings by date, play at up to 32x speed, grid view for multiple cameras
- Live view — watch any camera live directly in the browser (MSE, ~1-3s latency)
- Auto-reconnect — cameras that drop (reboot, network hiccup) reconnect automatically
- Watchdog — if a camera's recording process dies silently, it's respawned within 30s
- Disk cleanup — daily cron deletes recordings older than N days
- Mobile-friendly — responsive UI with swipe support and hamburger menu
- Per-camera audio — optional AAC transcoding for cameras with incompatible audio codecs
- Linux (Debian/Ubuntu, Fedora/RHEL, or Arch)
ffmpegjqpython3(stdlib only, no pip packages needed)- IP cameras with RTSP streams (Dahua, Hikvision, or any ONVIF-compatible camera)
git clone https://github.com/youruser/camrecord.git
cd camrecord
# copy and edit the config with your cameras and storage path
cp config.example.json config.json
nano config.json
# run the installer (installs packages, creates systemd services, sets up cron)
sudo bash install.shThen open http://<your-server-ip>:8080 in your browser.
{
"output_base": "/mnt/recordings",
"cameras": [
{
"name": "frontdoor",
"url": "rtsp://admin:password@192.168.1.100:554/cam/realmonitor?channel=1&subtype=0&unicast=true&proto=Onvif"
},
{
"name": "garage",
"url": "rtsp://admin:password@192.168.1.102:554/Streaming/Channels/101/",
"transcode_audio": true
}
]
}| Field | Description |
|---|---|
output_base |
Directory where recordings are stored. One subfolder per camera. |
cameras[].name |
Camera name. Used as folder name (lowercased) and shown in the UI. |
cameras[].url |
Full RTSP URL including credentials. |
cameras[].transcode_audio |
Optional. Set true if the camera uses G.711/PCMU audio (common on Hikvision). Transcodes to AAC. Default: false (stream copy). |
To add cameras later, edit config.json and restart the recorder:
sudo systemctl restart recordcamrecord/
├── install.sh # installer — run this first
├── config.json # your config (gitignored, has credentials)
├── config.example.json # safe example for reference
├── record.sh # recorder — spawns one ffmpeg per camera
├── server.py # web server — recordings + live streams + API
├── viewer.html # web UI
├── cleanup.sh # deletes recordings older than N days
├── cleanup.log # cleanup run history
├── record.service # systemd unit (generated by install.sh)
└── camara-web.service # systemd unit (generated by install.sh)
Recordings are stored in output_base:
/mnt/recordings/
├── frontdoor/
│ ├── 2026-05-01T00-00-00.mp4
│ ├── 2026-05-01T00-15-00.mp4
│ └── ...
└── backyard/
└── ...
| Service | Description |
|---|---|
record.service |
Runs record.sh — one ffmpeg process per camera, watchdog every 30s |
camara-web.service |
Runs server.py — serves the web UI on port 8080 |
# status
sudo systemctl status record
sudo systemctl status camara-web
# logs
journalctl -u record -f
journalctl -u camara-web -f
# restart after config changes
sudo systemctl restart recordThe installer adds a daily cron at 3am that deletes recordings older than 10 days:
0 3 * * * /path/to/camrecord/cleanup.sh 10 >> /path/to/camrecord/cleanup.log 2>&1
Change 10 to however many days you want to retain. To run it manually:
bash cleanup.sh 10| Key | Action |
|---|---|
Space |
Play / pause |
← / → |
Seek ±10s |
Shift + ← / → |
Seek ±3s |
↑ / ↓ |
Next / previous clip |
Shift + ↑ / ↓ |
Next / previous camera |
+ / - |
Speed up / slow down |
f |
Fullscreen |
Tested with:
- Dahua —
rtsp://user:pass@ip:554/cam/realmonitor?channel=1&subtype=0&unicast=true&proto=Onvif - Hikvision —
rtsp://user:pass@ip:554/Streaming/Channels/101/(settranscode_audio: trueif audio is broken) - Any ONVIF-compatible camera should work
MIT
