Show HN: CheapSecurity – Lightweight, Self-Hosted CCTV for Linux SBCs
Summary
CheapSecurity is a lightweight, self-hosted CCTV solution for Linux single-board computers, offering privacy-focused home monitoring with motion detection, recording, and Telegram alerts, all without cloud subscriptions.
View Cached Full Text
Cached at: 07/27/26, 01:41 AM
gmrandazzo/CheapSecurity
Source: https://github.com/gmrandazzo/CheapSecurity
CheapSecurity
This project provides a lightweight, self-hosted CCTV solution designed for Linux-based single-board computers (SBCs) and standard USB webcams. It offers an affordable, privacy-focused alternative for home monitoring by keeping your video data entirely under your control. Project Philosophy
- Privacy-First: By storing all footage locally, this system eliminates the need for third-party cloud subscriptions and ensures your data never leaves your network.
- Cost-Effective: Leverage existing hardware—such as a spare Linux board and a USB webcam—to build a fully functional surveillance system without recurring fees.
- Minimalist Architecture: The software is optimized to run efficiently on low-power devices, ensuring high performance even on entry-level hardware.
Key Features
- Hardware Agnostic: Highly compatible with a wide range of standard USB webcams.
- Resource Efficient: Optimized specifically for Linux-based boards (e.g., Raspberry Pi, Orange Pi, or similar SBCs).
- Data Sovereignty: Full control over your storage path, retention policies, and access methods.
- Simple Deployment: Designed for quick setup and easy maintenance.
Features
- Live MJPEG stream with a web dashboard
- Motion detection with frame differencing
- Automatic recording with a pre-motion buffer
- Email alerts with a snapshot picture when motion starts
- Telegram integration:
- Automatic video upload after motion is recorded
- Bot commands:
/snapshot,/video <seconds>,/help
- Night mode low-light enhancement (software CLAHE + brightness/contrast boost)
- Recordings bulk actions: select all, send to Telegram, download ZIP, delete
- Storage cleanup by age, total size, and emergency low-disk cleanup
- systemd autostart ready
- Licensed under GNU AGPLv3
Requirements
- Python 3.10 or newer
- OpenCV with V4L2 support (see installation options below)
- A USB webcam (
/dev/video0by default) - Optional: a Telegram bot token for Telegram notifications
- Optional: SMTP credentials for email alerts
Quick start
-
Verify OpenCV is available:
python3 -c "import cv2; print(cv2.__version__)"If OpenCV is not installed, you have two options:
- Option A — use a system OpenCV package (recommended on ARM boards such as the Odroid XU4, where a pre-built system package is usually optimized for the board).
- Option B — install OpenCV via pip (convenient on x86/amd64 machines or when no system package is available). Use
opencv-python-headlessbecause the app does not need a GUI.
-
Create and activate the virtual environment and install the app:
Option A: system OpenCV (recommended for ARM)
This creates a venv that can see the system site-packages, so the system
cv2is available inside the venv.cd $HOME/CheapSecurity python3 -m venv venv --system-site-packages source venv/bin/activate pip install -e .Option B: install everything via pip
Use this if you do not have a system OpenCV or prefer a self-contained venv.
cd $HOME/CheapSecurity python3 -m venv venv source venv/bin/activate pip install opencv-python-headless pip install -e .Note: On some older ARM boards the pip
opencv-python-headlesswheel may not be available or may be slow. If that happens, install OpenCV from your distribution’s package manager instead and use Option A. -
Find your webcam device (usually
/dev/video0):v4l2-ctl --list-devices -
Copy
config.json.exampletoconfig.jsonand edit it:cp config.json.example config.json nano config.json- Set camera device, resolution, and frame rate
- Fill in SMTP credentials if you want email alerts
- Fill in Telegram bot token and chat ID if you want Telegram uploads
Security:
config.jsonis listed in.gitignoreand must never be committed. It contains passwords and tokens. Always editconfig.json, notconfig.json.example. If you add a new setting, update both files so the example stays in sync. -
Run the app:
./venv/bin/python -m cheapsecurity.app -
Open the dashboard in a browser:
http://<odroid-ip>:5000
Configuration
Edit config.json:
| Section | Key | Description |
|---|---|---|
camera | device | V4L2 device index (0 = /dev/video0) |
camera | width, height, fps | Capture resolution and frame rate |
camera | night_mode | Enable low-light enhancement |
camera | night_mode_fps | Target FPS in night mode (camera may ignore this) |
camera | night_mode_gain | Target analog gain in night mode (camera may ignore this) |
camera | night_mode_brightness | Brightness boost in night mode |
camera | night_mode_contrast | Contrast boost in night mode |
motion | threshold | Pixel difference threshold (0-255) |
motion | min_area | Minimum contour area to trigger motion (full-res pixels) |
motion | cooldown_seconds | Keep recording after motion stops |
motion | scale | Downscale factor for motion detection (saves CPU) |
recording | dir | Where videos are saved |
recording | max_duration_seconds | Maximum length of one clip |
recording | pre_buffer_seconds | Seconds before motion included in clip |
recording | codec | Preferred FourCC codec (MJPG for low CPU, mp4v for smaller files) |
notifications | enabled | Send email alerts on motion |
notifications | smtp | SMTP server, port, username, password, TLS |
notifications | from, to, subject | Email sender/recipients/subject (use ["a@...", "b@..."] for multiple recipients) |
notifications | min_interval_minutes | Minimum time between alert emails |
telegram | enabled | Send videos to Telegram after motion is recorded |
telegram | bot_token, chat_id | Telegram Bot API token and destination chat |
telegram | send_video | Whether to upload the video file automatically |
telegram | min_interval_minutes | Minimum time between Telegram uploads |
telegram | poll_commands | Enable /snapshot, /video, and /help bot commands |
storage | max_age_days | Delete recordings older than this (default 3 days = 72h) |
storage | max_size_gb | Delete oldest files if total exceeds this |
storage | cleanup_interval_minutes | How often storage cleanup runs |
storage | delete_old_on_startup | If false, old recordings are kept when the app restarts |
storage | emergency_free_space_gb | If free disk space drops below this, delete old recordings before a new one |
storage | emergency_delete_count | How many oldest recordings to delete in an emergency cleanup |
web | host, port | Dashboard bind address and port |
web | stream_scale | Downscale factor for live stream (saves bandwidth/CPU) |
web.auth | enabled, username, password | Optional HTTP Basic Auth |
Telegram setup
1. Create a bot
- Open Telegram and message @BotFather.
- Send
/newbotand follow the prompts to choose a display name and username. - Copy the bot token (looks like
123456789:ABCdefGHIjklMNOpqrsTUVwxyz). - Keep this token secret — anyone with it can control your bot.
2. Get your chat ID
- Start a private chat with your new bot and send any message (for example,
/start). - Open this URL in a browser, replacing
<YOUR_BOT_TOKEN>with the real token:https://api.telegram.org/bot<YOUR_BOT_TOKEN>/getUpdates - Look for
"chat":{"id":123456789. The number is your chat ID.- If
getUpdatesis empty, send another message to the bot and refresh. - If you want to use a group chat, add the bot to the group first and send a message there; the chat ID will be negative for groups.
- If
- Copy the chat ID exactly, including the
-sign if it is a group.
3. Configure the app
Fill in the telegram section of config.json:
"telegram": {
"enabled": true,
"bot_token": "123456789:ABCdefGHIjklMNOpqrsTUVwxyz",
"chat_id": "123456789",
"send_video": true,
"min_interval_minutes": 5,
"poll_commands": true
}
Then restart the service:
sudo systemctl restart cheapsecurity@$(whoami).service
Automatic uploads
After a motion clip is saved, the video is uploaded to your Telegram chat. Uploads are rate-limited by min_interval_minutes.
Bot commands
From your configured chat, send:
/snapshot— receive the current camera picture/video 10— record and send a 10-second video (1–60 seconds, default 10)/help— list commands
The bot only responds to your configured chat_id.
Motion has priority: if the system is already recording because motion was detected, a /video request will not interrupt it. The bot will reply that a motion video is in progress and will be uploaded automatically.
Email alerts
Configure the notifications section in config.json. A picture from the moment motion starts is attached. Alerts are rate-limited by min_interval_minutes.
Gmail / Google Workspace setup
Google no longer allows “less secure apps” to use your regular Gmail password. You must create an App Password.
- Enable 2-Step Verification on your Google account:
- https://myaccount.google.com/signinoptions/two-step-verification
- Create an App Password:
- Go to https://myaccount.google.com/apppasswords
- Select app: Mail
- Select device: Other (Custom name) — type “CheapSecurity”
- Click Generate and copy the 16-character password (for example,
abcd efgh ijkl mnop).
- In
config.json, set:"notifications": { "enabled": true, "smtp": { "server": "smtp.gmail.com", "port": 465, "username": "[email protected]", "password": "abcdefghijklmnop", "use_tls": true }, "from": "[email protected]", "to": "[email protected]", "subject": "CheapSecurity motion alert", "min_interval_minutes": 5 }- Use the App Password (no spaces) in the
passwordfield, not your Google account password. - For Google Workspace accounts, the username is usually your full email address.
- The app uses implicit TLS (
SMTP_SSL) on the port you configure. Gmail accepts this on port 465.
- Use the App Password (no spaces) in the
Multiple recipients
"to": [
"[email protected]",
"[email protected]"
]
Night mode
Night mode combines:
- Software enhancement (CLAHE on the L channel)
- Camera brightness/contrast boost
- Attempts to lower FPS and raise gain/ISO if the camera supports it
Toggle it from the dashboard. It is applied to the live stream, recordings, and alert pictures.
Important: most USB webcams do not expose ISO/gain/exposure controls via V4L2, so FPS/gain adjustments may be ignored. True night vision requires an IR-sensitive camera and an IR illuminator.
Storage and cleanup
- Recordings are saved in
recordings/. - Recordings older than
max_age_daysare deleted during periodic cleanup, not on startup (unlessdelete_old_on_startupistrue). - If free disk space drops below
emergency_free_space_gb, the oldestemergency_delete_countrecordings are deleted before starting a new clip. - Recordings older than
max_age_daysor exceedingmax_size_gbare removed during periodic cleanup.
Web interface
- Live stream
- Status panel (resolution, FPS, recording state, motion state)
- Settings toggles: night mode, email notifications, Telegram uploads, built-in basic auth
- Recordings list with per-row checkboxes and bulk actions:
- Select all
- Send to Telegram
- Download selected (ZIP)
- Delete selected
Production deployment
Do not expose Flask’s development server to the internet. Use Gunicorn behind the built-in auth or another reverse proxy you trust.
1. Install Gunicorn
It is already defined in pyproject.toml:
cd $HOME/CheapSecurity
source venv/bin/activate
pip install -e .
2. Run with systemd + Gunicorn
Copy the service template and enable it from a user shell:
sudo cp cheapsecurity.service /etc/systemd/system/[email protected]
sudo systemctl daemon-reload
sudo systemctl enable --now cheapsecurity@$(whoami).service
This binds Gunicorn to 0.0.0.0:5000 with one worker and four threads, so the dashboard and stream are reachable directly on your network. Only one worker is used because the camera must be opened by a single process.
Security: if you expose this to the internet, put a reverse proxy with HTTPS and authentication in front of Gunicorn. If you only access it locally, keep the built-in auth enabled.
View logs:
sudo journalctl -u cheapsecurity@$(whoami).service -f
Project structure
CheapSecurity/
├── src/
│ └── cheapsecurity/ # Python package
│ ├── app.py # Development launcher
│ ├── cctv.py # Motion detection, recording, alerts, Telegram bot
│ ├── web.py # Flask dashboard and APIs
│ ├── wsgi.py # Production WSGI entry point
│ ├── diagnose.py # Diagnostic/troubleshooting script
│ ├── templates/ # HTML templates
│ └── static/ # CSS/JS
├── tests/ # Test suite
├── config.json # Your local settings (gitignored, never commit)
├── config.json.example # Example settings template (committed)
├── pyproject.toml # Package metadata and dependencies
├── cheapsecurity.service # systemd template
├── LICENSE # GNU AGPLv3
└── recordings/ # Saved videos
Troubleshooting
If recordings stop appearing:
- Check the service is running:
sudo systemctl status cheapsecurity@$(whoami).service - Check logs:
sudo journalctl -u cheapsecurity@$(whoami).service -f - Run the diagnostic script:
source venv/bin/activate python -m cheapsecurity.diagnose - Try lowering
motion.min_areaif no motion is detected.
License
This project is licensed under the GNU Affero General Public License v3.0 or later (AGPLv3). See LICENSE.
Similar Articles
Show HN: Open-source private home security camera system (end-to-end encryption)
Secluso is an open-source, end-to-end encrypted home security camera system for Raspberry Pi that allows remote access without cloud surveillance. It features a 5-minute setup and reproducible builds.
The Best Subscription-Free Home Security Cameras I’ve Tried
Wired reviews the best subscription-free home security cameras that store footage locally, highlighting Eufy, Synology, TP-Link Tapo, and Aqara as top brands for privacy-conscious users.
The Best Motion Sensors and Home Security Gadgets Without Cameras
This article reviews motion sensors and home security gadgets that don't use cameras, offering privacy-focused alternatives for detecting activity.
@tom_doerr: Wi-Fi motion detection without cameras https://github.com/francescopace/espectre…
ESPectre is an open-source tool that uses Wi-Fi channel state information (CSI) on ESP32 devices for privacy-preserving motion detection, with native Home Assistant integration via ESPHome.
@0x0SojalSec: fully uncensored Cybersecurity-specialized model tuned on exploits, pentesting and Run locally on MacBook, delivers exp…
A fully uncensored cybersecurity-specialized AI model fine-tuned on exploits and pentesting data, designed to run locally on consumer hardware with multiple quantization options, offering expert offensive and defensive insights.