Turn any Linux device with a camera into a motion detection security system you control.
Self-hosted. Privacy-first. No cloud required.
OpenSentry - LMV transforms any Linux device with a webcam into a motion detection security camera. Stream live video with motion detection overlays β all processed locally on your hardware.
- πΉ Live Video Streaming - Real-time camera feed with motion detection overlay
- πΆ Motion Detection - MOG2 background subtraction with configurable sensitivity
- πΈ Snapshot Capture - Manual snapshots and automatic capture on motion events
- π Web Interface - Clean, dark-themed, mobile-friendly dashboard
- π Flexible Authentication - Local auth or integrate with OAuth2/OIDC providers
- π mDNS Discovery - Auto-discover devices on your network
- π³ Docker Ready - Deploy anywhere with Docker Compose
- Quick Start
- Architecture
- Installation
- Authentication
- Configuration
- API & Endpoints
- Snapshot Features
- OAuth2 Setup Guide
- Discovery & mDNS
- Troubleshooting
- Related Projects
# Clone the repository
git clone https://github.com/Sbussiso/OpenSentry.git
cd OpenSentry
# Start the server
uv run server.py
# Access at http://127.0.0.1:5000
# Default credentials: admin / admin# Build and start using the included compose.yaml
DOCKER_HOST=unix:///var/run/docker.sock docker compose -f compose.yaml build --pull
DOCKER_HOST=unix:///var/run/docker.sock docker compose -f compose.yaml up -d
# Tail logs (look for "TurboJPEG enabled for JPEG encoding")
DOCKER_HOST=unix:///var/run/docker.sock docker compose -f compose.yaml logs -f opensentry
# Access at http://127.0.0.1:5000Note:
- If you see a warning about multiple compose files, specifying
-f compose.yamlavoids ambiguity. - If you donβt have a camera, set
OPENSENTRY_ALLOW_PLACEHOLDER=1incompose.yaml.
That's it! OpenSentry is now streaming from your camera.
OpenSentry is designed for low CPU usage while supporting multiple simultaneous viewers.
server.py: App entrypoint. Defines routes, lifecycle, and starts background services.helpers/camera.py:CameraStreamcaptures frames from V4L2 (/dev/video*) with low-latency settings.helpers/frame_hub.py:Broadcastercentralizes encoding and fan-out:- Single producer function (
produce_fn) encodes one JPEG per tick. - Single-slot latest-frame buffer drops backlog to keep latency low.
- Single producer function (
helpers/encoders.py: Uses TurboJPEG when available, otherwise OpenCV JPEG.- Background workers (in
server.py):_MotionWorkerdetects motion on downscaled frames, draws ROI, and publishes tomotion_broadcaster.- Raw stream uses a lightweight producer to encode frames for
raw_broadcaster.
graph TD
A[Camera /dev/video0] -->|BGR frames| B[CameraStream]
B --> C1[Raw Producer]
C1 --> D1[raw_broadcaster]
D1 --> E1[Clients /video_feed]
B --> C2[_MotionWorker]
C2 --> D2[motion_broadcaster]
D2 --> E2[Clients /video_feed_motion]
subgraph Encoding
X[helpers/encoders.py] -->|TurboJPEG or OpenCV| D1
X --> D2
end
- Single encode per tick per stream type (raw/motion), shared by all clients.
- Latest-frame only buffer avoids growing queues and keeps latency low.
- FPS caps for workers prevent CPU spikes; output width and JPEG quality configurable.
- TurboJPEG accelerates JPEG encoding when the native lib is present.
- Optimized for lightweight motion detection with minimal CPU overhead.
OpenSentry performs real-time motion detection with video streaming, which requires reasonable CPU and memory. Here's what to expect on different hardware:
| Hardware | Performance | Notes |
|---|---|---|
| Desktop/Laptop (2+ cores, 4GB+ RAM) | βββββ Excellent | 30+ fps at 1080p, multiple clients, very smooth |
| Intel NUC / Mini PC | βββββ Excellent | 25-30 fps at 1080p, ideal for production |
| Old Desktop (2010+, 2GB RAM) | ββββ Good | 15-20 fps at 720p, perfectly usable |
| Model | CPU | RAM | Performance | FPS @ 640x480 | FPS @ 1080p | Status |
|---|---|---|---|---|---|---|
| Pi Zero W | 1-core 1GHz ARMv6 | 512MB | β Not Recommended | 1-3 fps | N/A | Too slow, frequent crashes |
| Pi Zero 2 W | 4-core 1GHz ARMv8 | 512MB | 5-8 fps | N/A | Usable but laggy | |
| Pi 3B | 4-core 1.2GHz ARMv8 | 1GB | βββ Acceptable | 10-15 fps | 5-8 fps | Minimum recommended |
| Pi 3B+ | 4-core 1.4GHz ARMv8 | 1GB | βββ Good | 12-18 fps | 8-12 fps | Solid choice |
| Pi 4 (2GB) | 4-core 1.5GHz ARMv8 | 2GB | ββββ Very Good | 20-25 fps | 15-20 fps | Great performance |
| Pi 4 (4/8GB) | 4-core 1.8GHz ARMv8 | 4-8GB | βββββ Excellent | 25-30 fps | 18-25 fps | Recommended for production |
| Pi 5 (4/8GB) | 4-core 2.4GHz ARMv8 | 4-8GB | βββββ Excellent | 30+ fps | 25-30 fps | Best Raspberry Pi option |
FPS (Frames Per Second):
- 30 fps - Smooth, real-time video (broadcast quality)
- 15-20 fps - Acceptable, minor stuttering
- 10-15 fps - Usable for security monitoring
- <10 fps - Laggy, frustrating experience
Optimization Tips for Lower-End Hardware:
- Use 640x480 resolution (default recommended for Pi 3B/3B+)
- Lower JPEG quality to 60-70% in settings
- Reduce stream FPS to 10-12 in settings
- Keep default configuration: 1 Gunicorn worker with gevent
- Use wired Ethernet instead of WiFi when possible
| Use Case | Recommended Hardware | Why |
|---|---|---|
| Budget / Testing | Raspberry Pi 3B/3B+ | Acceptable performance at ~$25-35 used |
| Best Value | Raspberry Pi 4 (2GB) | Great performance at ~$45, good for most setups |
| Production / Multiple Cameras | Raspberry Pi 4/5 (4GB+) | Smooth operation, handles multiple clients |
| Desktop/Server | Any modern x86_64 PC | Overkill but excellent if you have one available |
- Raspberry Pi Zero W - Too slow (1-core ARMv6), will frustrate you
- Raspberry Pi 1/2 - Outdated, poor performance with modern Python/OpenCV
- Virtual machines with <2GB RAM - Motion detection is memory-intensive
- Python 3.12+ (for source installation)
- Linux with V4L2 camera support (e.g.,
/dev/video0) - Docker (optional, for containerized deployment)
# Install dependencies with uv (recommended)
uv sync
# Or use pip
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
# Start the server
uv run server.pyVisit http://127.0.0.1:5000 and log in with admin/admin.
The repo includes a compose.yaml preconfigured to map /dev/video0 and set sane performance defaults. Optimized for Raspberry Pi and ARM devices.
# Build image and start the service
docker compose build --no-cache
docker compose up -d
# Logs and status
docker logs -f opensentry
docker compose psFirst-time setup on Raspberry Pi:
# Clone the repo
git clone https://github.com/Sbussiso/OpenSentry.git
cd OpenSentry
# Generate lockfile (uses opencv-python-headless for ARM stability)
uv lock
# Build and start
docker compose build --no-cache
docker compose up -d
# Monitor logs (camera init takes 10-60s on Pi)
docker logs -f opensentryservices:
opensentry:
image: opensentry:latest
container_name: opensentry
ports:
- "5000:5000"
environment:
- OPENSENTRY_USER=admin
- OPENSENTRY_PASS=admin
- OPENSENTRY_SECRET=please-change-me-in-production
- OPENSENTRY_PORT=5000
devices:
- /dev/video0:/dev/video0
group_add:
- video
restart: unless-stopped# Build and start
docker compose up -d --build
# View logs
docker logs -f opensentryFor production deployments with OAuth2:
services:
opensentry:
build: .
container_name: opensentry
ports:
- "${OPENSENTRY_PORT:-5000}:${OPENSENTRY_PORT:-5000}"
environment:
- OPENSENTRY_USER=admin
- OPENSENTRY_PASS=${OPENSENTRY_PASS}
- OPENSENTRY_SECRET=${OPENSENTRY_SECRET}
- OPENSENTRY_PORT=${OPENSENTRY_PORT:-5000}
- OPENSENTRY_DEVICE_NAME=Front Door Camera
- OPENSENTRY_API_TOKEN=${API_TOKEN}
- OPENSENTRY_LOG_LEVEL=INFO
devices:
- /dev/video0:/dev/video0
group_add:
- video
volumes:
- opensentry_config:/app
restart: unless-stopped
volumes:
opensentry_config:Create a .env file:
OPENSENTRY_PASS=secure-password-here
OPENSENTRY_SECRET=your-64-char-random-secret
OPENSENTRY_PORT=5000
API_TOKEN=your-api-token-for-status-endpointOpenSentry supports two authentication modes: Local (simple username/password) and OAuth2 (enterprise SSO integration).
Simple username/password authentication for quick setups.
Configuration:
# Environment variables
export OPENSENTRY_USER=admin
export OPENSENTRY_PASS=admin
export OPENSENTRY_SECRET=please-change-meDocker:
environment:
- OPENSENTRY_USER=admin
- OPENSENTRY_PASS=secure-password
- OPENSENTRY_SECRET=random-64-char-secretIntegrate with external OAuth2/OIDC providers for centralized authentication, SSO, and enterprise identity management.
Supported Providers:
- β Custom OAuth2 Server (see our OAuth2 project)
- β Keycloak
- β Auth0
- β Okta
- β Google OAuth2
- β Any OIDC-compliant provider
Benefits:
- π’ Single Sign-On (SSO) - One login for all your services
- π Centralized Auth - Manage users in one place
- π Audit Logging - Track authentication events
- π Advanced Security - MFA, conditional access, etc.
- π Multi-Device - Consistent auth across all OpenSentry instances
Configuration via Web UI:
- Navigate to Settings β Authentication
- Select "OAuth2 Authentication"
- Enter your OAuth2 server details:
- Base URL:
http://127.0.0.1:8000(or your OAuth2 server address) - Client ID:
opensentry-device - Client Secret: (optional, for confidential clients)
- Scope:
openid profile email offline_access
- Base URL:
- Click "Test OAuth2 Connection" to verify
- Click "Save Authentication Settings"
- Restart OpenSentry
Configuration via JSON:
Edit config.json:
{
"auth": {
"auth_mode": "oauth2",
"oauth2_base_url": "http://127.0.0.1:8000",
"oauth2_client_id": "opensentry-device",
"oauth2_client_secret": "",
"oauth2_scope": "openid profile email offline_access"
}
}Fallback to Local Login:
If the OAuth2 server is unavailable, users can access local login via:
http://your-opensentry:5000/oauth2/fallback
Or click "Use local login for now" on the OAuth2 unavailable page.
| Variable | Description | Default |
|---|---|---|
OPENSENTRY_USER |
Local auth username | admin |
OPENSENTRY_PASS |
Local auth password | admin |
OPENSENTRY_SECRET |
Session encryption key (use random 64-char string) | Random (dev only) |
OPENSENTRY_PORT |
HTTP port (auto-increments if busy) | 5000 |
OPENSENTRY_CAMERA_INDEX |
Preferred camera index | 0 |
OPENSENTRY_DEVICE_NAME |
Device display name | OpenSentry |
OPENSENTRY_LOG_LEVEL |
Logging verbosity (INFO, DEBUG) |
INFO |
OPENSENTRY_API_TOKEN |
Bearer token for /status endpoint |
(none) |
OPENSENTRY_MDNS_DISABLE |
Disable mDNS advertisement | 0 |
OPENSENTRY_VERSION |
Version metadata for discovery | 0.1.0 |
GUNICORN_WORKERS |
Number of Gunicorn workers (use 1 to prevent camera contention) | 1 |
GUNICORN_WORKER_CLASS |
Worker type: gevent for concurrent handling, sync for debugging |
gevent |
GUNICORN_TIMEOUT |
Worker timeout in seconds | 60 |
OPENCV_VIDEOIO_PRIORITY_LIST |
OpenCV video backend priority | V4L2 |
{
"device_id": "a7077099be8a",
"motion_detection": {
"min_area": 500,
"pad": 10,
"mog2_var_threshold": 16,
"mog2_history": 500
},
"snapshots": {
"enabled": false,
"cooldown": 15,
"motion_threshold": 5000,
"directory": "snapshots"
},
"auth": {
"auth_mode": "local",
"oauth2_base_url": "",
"oauth2_client_id": "",
"oauth2_client_secret": "",
"oauth2_scope": "openid profile email offline_access"
},
"video": {
"width": 0,
"height": 0,
"fps": 15,
"mjpeg": true
},
"stream": {
"max_width": 960,
"jpeg_quality": 75,
"raw_fps": 15
}
}Motion Detection Parameters:
min_area- Minimum contour area in pixels to trigger detection (default: 500)pad- Padding around detection boxes in pixels (default: 10)mog2_var_threshold- MOG2 variance threshold: 8-12 for high sensitivity, 18-30 for fewer false positives (default: 16)mog2_history- Learning period in frames: 200-500 for fast adaptation, 500-1000 for stable scenes (default: 500)
Automatic Snapshots:
enabled- Toggle automatic snapshots on motion detection (default: false)cooldown- Minimum seconds between snapshots to prevent spam (default: 15)motion_threshold- Minimum total motion area in pixels to trigger snapshot (default: 5000)directory- Directory to save snapshots, relative to app directory (default: "snapshots")
| Endpoint | Description | Auth Required |
|---|---|---|
/ |
Motion detection camera dashboard | β |
/video_feed |
Raw camera feed (MJPEG) | β |
/video_feed_motion |
Motion detection overlay (MJPEG) | β |
/settings |
Configuration page | β |
/health |
Health check (200 OK) | β |
| Endpoint | Description |
|---|---|
/login |
Local login page |
/logout |
Clear session and logout |
/oauth2/login |
Initiate OAuth2 authorization flow |
/oauth2/callback |
OAuth2 callback handler |
/oauth2/fallback |
Enable local login fallback |
| Endpoint | Method | Description | Auth |
|---|---|---|---|
/status |
GET | Device status JSON | Bearer token (if configured) |
/api/snapshot |
GET | Capture and download current frame as JPEG | β |
/api/oauth2/test |
GET | Test OAuth2 connectivity | β |
Example /status Response:
{
"id": "a7077099be8a",
"name": "OpenSentry",
"version": "0.1.0",
"port": 5000,
"caps": ["raw", "motion"],
"routes": {
"raw": true,
"motion": true
},
"camera": {
"running": true,
"has_frame": true
},
"auth_mode": "session"
}OpenSentry provides two ways to capture snapshots from your camera feed:
Click the "Take Snapshot" button on the main dashboard to instantly download a JPEG image with motion detection overlays.
- Endpoint:
/api/snapshot - Format: JPEG with motion detection boxes and status overlay
- Filename:
opensentry-snapshot-YYYY-MM-DD_HH-MM-SS.jpg - Download: Automatically downloads to your browser's download folder
Configure OpenSentry to automatically save snapshots when motion is detected. Perfect for security monitoring and event recording.
Configuration (Settings Page or config.json):
"snapshots": {
"enabled": true,
"cooldown": 15,
"motion_threshold": 5000,
"directory": "snapshots"
}How It Works:
- Motion is detected and total motion area is calculated
- If motion area exceeds
motion_threshold(in pixels) - And
cooldownperiod has elapsed since last snapshot - Frame is automatically saved to
snapshots/directory - Filename format:
YYYY-MM-DD_HH-MM-SS_motion.jpg
Usage Examples:
High Security (Capture Most Motion):
{
"cooldown": 5,
"motion_threshold": 2000
}Balanced Monitoring (Default):
{
"cooldown": 15,
"motion_threshold": 5000
}Critical Events Only:
{
"cooldown": 60,
"motion_threshold": 15000
}Docker Volume Mounting:
volumes:
- ./snapshots:/app/snapshots # Persist snapshots on host- OAuth2 Server Running - See our OAuth2 project or use your existing provider
- Client Registration - Register OpenSentry as an OAuth2 client
For our custom OAuth2 server, use the included registration script:
# Navigate to OAuth2 server directory
cd ../Oauth2
# Create and run the client registration script
cat > add_opensentry_client.py << 'EOF'
#!/usr/bin/env python3
import os
import sys
os.environ['DATABASE_URL'] = os.environ.get('DATABASE_URL', 'sqlite:///oauth.db')
from server import SessionLocal, OAuth2Client
db = SessionLocal()
existing = db.query(OAuth2Client).filter_by(client_id='opensentry-device').first()
# Space-separated redirect URIs
redirect_uris = 'http://localhost:5000/oauth2/callback http://127.0.0.1:5000/oauth2/callback'
scope = 'openid profile email offline_access'
if existing:
print("Updating existing client...")
existing.client_secret = None
existing.client_name = 'OpenSentry Device'
existing.redirect_uris = redirect_uris
existing.scope = scope
existing.grant_types = 'authorization_code refresh_token'
existing.response_types = 'code'
existing.token_endpoint_auth_method = 'none'
existing.require_consent = True
db.commit()
print("β Client 'opensentry-device' updated")
else:
print("Creating new client...")
client = OAuth2Client(
client_id='opensentry-device',
client_secret=None,
client_name='OpenSentry Device',
redirect_uris=redirect_uris,
scope=scope,
grant_types='authorization_code refresh_token',
response_types='code',
token_endpoint_auth_method='none',
require_consent=True
)
db.add(client)
db.commit()
print("β Client 'opensentry-device' created")
db.close()
EOF
# Run the registration script
uv run python add_opensentry_client.pyImportant Configuration Details:
- Client Type: Public (no client secret)
- Auth Method:
none(PKCE required) - Redirect URIs: Must include both
localhostand127.0.0.1variants - Grant Types:
authorization_code,refresh_token - Scopes:
openid profile email offline_access
Via Web UI:
- Start OpenSentry:
uv run server.py - Navigate to
/settings - Scroll to Authentication Settings
- Select OAuth2 Authentication
- Enter configuration:
OAuth2 Server Base URL: http://127.0.0.1:8000 Client ID: opensentry-device Client Secret: (leave empty) Scope: openid profile email offline_access - Click Test OAuth2 Connection (should show "Success!")
- Click Save Authentication Settings
- Restart OpenSentry
Via Configuration File:
Edit config.json:
{
"auth": {
"auth_mode": "oauth2",
"oauth2_base_url": "http://127.0.0.1:8000",
"oauth2_client_id": "opensentry-device",
"oauth2_client_secret": "",
"oauth2_scope": "openid profile email offline_access"
}
}Restart OpenSentry.
- Navigate to
http://127.0.0.1:5000 - You'll be redirected to the OAuth2 server login
- Log in with your OAuth2 credentials
- Approve the consent screen
- You'll be redirected back to OpenSentry and logged in
If OAuth2 server is down:
- You'll see an "OAuth2 Unavailable" page
- Click "Use local login for now" to use basic auth
- Or visit
http://127.0.0.1:5000/oauth2/fallback
When running multiple OpenSentry devices with OAuth2:
Option 1: One Client, Multiple Redirect URIs
redirect_uris = 'http://localhost:5000/oauth2/callback http://localhost:5001/oauth2/callback http://localhost:5002/oauth2/callback'Option 2: Separate Clients Per Device (Recommended)
# Register each device separately
opensentry-garage β http://192.168.1.10:5000/oauth2/callback
opensentry-front β http://192.168.1.11:5000/oauth2/callback
opensentry-backyard β http://192.168.1.12:5000/oauth2/callbackUpdate each OpenSentry's config.json with its unique oauth2_client_id.
OpenSentry advertises itself on the local network using mDNS (Zeroconf).
- Service Type:
_opensentry._tcp.local - Port: Actual bound port (may differ from
OPENSENTRY_PORTif port was busy)
| Key | Description | Example |
|---|---|---|
id |
Persistent device ID | a7077099be8a |
name |
Device display name | Front Door Camera |
ver |
OpenSentry version | 0.1.0 |
caps |
Capabilities | raw,motion |
auth |
Auth mode | session or token |
api |
Available APIs | /status,/health |
path |
Web UI path | / |
proto |
Protocol version | 1 |
Use the companion OpenSentry Command project to discover all devices on your network:
# Clone OpenSentry Command
git clone https://github.com/Sbussiso/OpenSentry-Command.git
cd OpenSentry-Command
# Run discovery
uv run main.py
# Access at http://127.0.0.1:3000See Related Projects for more info.
Problem: Workers crash with "code 139" (segmentation fault) on Raspberry Pi
Solution: This is fixed in the latest version. If you're still experiencing crashes:
-
Ensure you have the latest changes:
git pull rm uv.lock # Force regeneration with opencv-python-headless uv lock docker compose build --no-cache docker compose up -
Verify compose.yaml has correct configuration:
environment: - GUNICORN_WORKERS=1 # Single worker to prevent camera contention - GUNICORN_WORKER_CLASS=gevent # Async worker for concurrent handling - OPENBLAS_NUM_THREADS=1 - OPENCV_VIDEOIO_PRIORITY_LIST=V4L2
-
Check that opencv-python-headless is being used:
docker exec opensentry pip list | grep opencv # Should show: opencv-python-headless (not opencv-python)
Root Cause: Multiple workers + opencv-python + OpenBLAS threading causes camera contention and segfaults on ARM. The fix uses:
opencv-python-headless(more stable on ARM)- Single worker with
geventfor concurrent request handling - Single-threaded OpenBLAS
Problem: Camera initialization is slow on Raspberry Pi
Solution: This is normal. The Pi's camera takes 10-60 seconds to initialize. Check logs:
docker logs -f opensentry
# Wait for: "INFO opensentry.camera: Opened camera device=/dev/video0"Problem: High CPU usage on Raspberry Pi
Solutions:
- Reduce camera resolution in settings (640x480 recommended)
- Lower JPEG quality to 60-70%
- Lower stream FPS in settings
- Keep default configuration (1 worker with gevent handles concurrency efficiently)
- Reduce motion detection processing frequency
- Disable automatic snapshots if not needed
Problem: Camera not detected or streams are blank / can't open camera by index error in Docker logs
Root Cause: USB cameras often require privileged mode in Docker due to low-level device access requirements.
Solution #1 (Most Common): Enable privileged mode in compose.yaml:
services:
opensentry:
privileged: true # Required for camera access with some USB cameras
devices:
- /dev/video0:/dev/video0
group_add:
- videoThen restart:
docker compose down
docker compose up -d
docker logs -f opensentry # Should see "Opened camera device=/dev/video0"Other Solutions:
-
Check camera device exists on host:
ls -l /dev/video* -
Test camera with v4l2:
sudo apt install v4l-utils v4l2-ctl --list-devices v4l2-ctl -d /dev/video0 --list-formats-ext
-
Verify device is not in use:
lsof /dev/video0 # If in use, stop other applications using the camera -
Check logs:
# Host uv run server.py # Watch startup logs # Docker docker logs -f opensentry
-
Try different camera index:
export OPENSENTRY_CAMERA_INDEX=1 uv run server.py
Problem: Snapshot button hangs/loads forever
Root Cause: Using sync workers causes blocking - the video stream occupies the worker, preventing snapshot requests from being handled.
Solution: Use gevent worker class in compose.yaml (default configuration):
environment:
- GUNICORN_WORKERS=1 # Single worker to prevent camera contention
- GUNICORN_WORKER_CLASS=gevent # Async worker allows concurrent requestsThen rebuild and restart:
docker compose down
docker compose up -d --buildHow it works:
geventuses greenlets to handle multiple concurrent requests within a single worker- Video streams and API requests (like snapshots) can be processed simultaneously
- No camera contention since only one worker accesses the camera device
Problem: "OAuth2 Server Unavailable"
Solutions:
-
Verify OAuth2 server is running:
curl http://127.0.0.1:8000/.well-known/openid-configuration
-
Check network connectivity from OpenSentry to OAuth2 server
-
Review OAuth2 server logs for errors
-
Use fallback:
http://127.0.0.1:5000/oauth2/fallback
Problem: "Invalid OAuth2 callback" or redirect errors
Solutions:
-
Verify redirect URI matches exactly:
# In OAuth2 client config redirect_uris = 'http://localhost:5000/oauth2/callback' # Must match OpenSentry's callback URL # Format: http://<host>:<port>/oauth2/callback
-
Include both
localhostand127.0.0.1variants:redirect_uris = 'http://localhost:5000/oauth2/callback http://127.0.0.1:5000/oauth2/callback' -
For remote access, add your OpenSentry's IP:
redirect_uris = 'http://192.168.1.10:5000/oauth2/callback'
Problem: "Token exchange failed" (401 error)
Solutions:
-
Verify client is registered correctly:
# Check client configuration uv run python -c "from server import SessionLocal, OAuth2Client; \ db = SessionLocal(); \ c = db.query(OAuth2Client).filter_by(client_id='opensentry-device').first(); \ print('Auth method:', c.token_endpoint_auth_method); \ print('Client secret:', c.client_secret); \ print('Redirect URIs:', c.redirect_uris)"
-
Ensure
token_endpoint_auth_method = 'none'for public clients -
Verify redirect_uris are stored as space-separated string, not JSON array
-
Check OAuth2 server logs for detailed error messages
Problem: Session lost during OAuth2 flow
Solutions:
-
Ensure
OPENSENTRY_SECRETis set and consistent:export OPENSENTRY_SECRET="your-64-character-random-secret"
-
Use Docker volumes to persist sessions:
volumes: - opensentry_config:/app
-
Check browser cookie settings (ensure cookies enabled)
Problem: mDNS/Zeroconf not working
Solutions:
-
Check if Avahi/Bonjour is running:
sudo systemctl status avahi-daemon
-
Disable mDNS if not needed:
export OPENSENTRY_MDNS_DISABLE=1 -
Docker: Ensure container can broadcast on LAN (use
network_mode: hostor proper bridge config)
Problem: Port 5000 already in use
Solution: OpenSentry auto-increments to next available port (5001, 5002, etc.)
To force a specific port:
export OPENSENTRY_PORT=5010
uv run server.pyCheck actual bound port in logs:
[INFO] opensentry: Binding HTTP server on port 5010
Problem: High CPU usage or slow streams
Solutions:
-
Reduce camera resolution via
/settings(640x480 recommended for Raspberry Pi) -
Lower JPEG quality and stream FPS via
/settings -
Increase Docker resource limits:
deploy: resources: limits: cpus: '2.0' memory: 2G
-
Use hardware acceleration if available (Raspberry Pi: enable V4L2 hardware encoding)
-
Ensure TurboJPEG is enabled
- In containers:
libturbojpeg0is installed by the Dockerfile; it should auto-enable. - On host: install PyTurboJPEG and the native lib, then export the path if needed:
uv add PyTurboJPEG # Ubuntu 24.04 sudo apt-get install -y libturbojpeg libjpeg-turbo8 libjpeg-turbo8-dev export TURBOJPEG=/lib/x86_64-linux-gnu/libturbojpeg.so.0
- In containers:
-
Cap CPU-heavy thread pools (inside Docker already set in compose)
export OMP_NUM_THREADS=1 OPENBLAS_NUM_THREADS=1 MKL_NUM_THREADS=1 NUMEXPR_NUM_THREADS=1 -
Use default configuration (1 worker with gevent) for best efficiency
export GUNICORN_WORKERS=1 export GUNICORN_WORKER_CLASS=gevent
-
If network constrained, reduce
JPEG_QUALITYandOUTPUT_MAX_WIDTHvia/settings
Device Discovery & Management Dashboard
Central dashboard to discover, monitor, and manage all OpenSentry devices on your network.
- π Auto-discover devices via mDNS
- π Network scanning and device status
- π Unified access to all cameras
- π OAuth2 integration for SSO
Self-Hosted OAuth2 / OIDC Authorization Server
Complete OAuth2/OIDC server for centralized authentication across all your services.
- π Enterprise-grade security (PKCE, refresh tokens, RS256)
- ποΈ Admin UI for client management
- π Standards-compliant (OAuth 2.0, OIDC)
- π Production-ready
Quick Setup with OpenSentry:
# 1. Start OAuth2 server
cd ../Oauth2
uv run server.py
# 2. Register OpenSentry client (see OAuth2 Setup Guide above)
# 3. Configure OpenSentry to use OAuth2 (via /settings)Contributions welcome! Whether it's bug reports, feature requests, or code contributions.
- π Report bugs: Open an issue
- π‘ Request features: Start a discussion
- π§ Submit code: Fork, develop, and create a pull request
MIT License - See LICENSE for details.
Free to use, modify, and deploy anywhere.
- Change default credentials (
OPENSENTRY_USER,OPENSENTRY_PASS) - Generate strong random
OPENSENTRY_SECRET(64+ characters) - Use HTTPS with reverse proxy (nginx, Traefik, Caddy)
- Set up
OPENSENTRY_API_TOKENfor/statusendpoint protection - Enable OAuth2 for centralized authentication
- Regular backups of
config.json - Keep dependencies updated (
uv syncorpip install --upgrade) - Monitor logs for unauthorized access attempts
- Use firewall rules to restrict access to trusted networks
- Disable mDNS in production (
OPENSENTRY_MDNS_DISABLE=1)
server {
listen 443 ssl http2;
server_name opensentry.example.com;
ssl_certificate /path/to/cert.pem;
ssl_certificate_key /path/to/key.pem;
location / {
proxy_pass http://127.0.0.1:5000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# WebSocket support for live streams
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}- β Privacy-First - All processing happens locally, no cloud required
- β Self-Hosted - You control your data and infrastructure
- β Lightweight - Focused on motion detection for efficient resource usage
- β Flexible Authentication - Start simple, scale with OAuth2/SSO
- β Network Discovery - Auto-discover devices via mDNS
- β Open Source - Audit, modify, and extend as needed
- β Production-Ready - Used in real-world deployments
- β Free Forever - MIT license, no hidden costs
Take control of your motion detection camera system.
Self-hosted. Private. Yours.