Note: This file guides AI coding agents (Claude Code, GitHub Copilot, Cursor, etc.) working on ZoneMinder. CLAUDE.md is a symlink to this file.
- Testing First: Write tests BEFORE/DURING implementation - NEVER skip. Tests written "later" never get written.
- Build System: CMake with C++17, out-of-source builds in
build/. In-source builds pollute the repo. - Feature Workflow: GitHub Issue -> Feature Branch -> Implement FULLY -> Tests Pass -> Get Approval -> Merge to master. Feature branches keep master stable.
- Commits: Conventional format (
feat:/fix:/test:), reference issues (refs #norfixes #n). Enables automated changelog. - Pre-Commit: Tests pass, build succeeds, linting clean, no warnings.
- Never merge without user approval. Never leave features half-implemented.
Linux-based CCTV surveillance system: capture, analysis, recording, and monitoring of video cameras.
- C++ daemons (
src/) - Capture (zmc), analysis (zma), streaming (zms), utility (zmu) - PHP web interface (
web/) - Bootstrap + jQuery UI, AJAX endpoints inweb/ajax/, views inweb/views/ - REST API (
web/api/) - CakePHP 2.x, controllers inweb/api/app/Controller/, JWT auth - Perl scripts (
scripts/) - Daemon control (zmdc.pl), migrations (zmupdate.pl), filtering (zmfilter.pl) - MySQL database (
db/) - Schema indb/zm_create.sql.in, migrations indb/zm_update-*.sql(60+ versions)
Camera -> zmc (capture) -> Shared Memory -> zma (analysis) -> Event Recording
| |
zms (streaming) Database + Disk
| |
Web Browsers <- Web Interface/API <- MySQL Storage
- Shared Memory:
zmcwrites frames to/dev/shm;zmaandzmsread from same buffers. Zero-copy for performance — this is why ZM can handle many cameras on modest hardware. - Monitor-Centric:
Monitorclass (src/zm_monitor.cpp/h) is the central orchestrator. One monitor = one DB row = one set of daemons. Most changes to camera handling flow through this class. - Pluggable Cameras: Abstract
Camerabase with:LocalCamera(V4L2),RemoteCameraRTSP,RemoteCameraHTTP,FFmpegCamera,LibVLCCamera,LibVNCCamera. Add new camera types by subclassing Camera. - Event-Driven Recording: Motion detection triggers
Eventobjects with pre/post alarm buffers. Lifecycle: Create -> Record -> Close -> Archive/Delete. Events are the core unit of recorded footage. - Multi-Server Clustering: Database-coordinated distributed architecture with shared monitors and storage.
src/ C++ core (86+ source files): zm_monitor.*, zm_camera.*, zm_event.*, zm_zone.*, zm_image.*, zm_ffmpeg*.*
web/ PHP web interface
ajax/ AJAX handlers
includes/ PHP libraries and functions
views/ UI templates
skins/ Themes (classic skin)
js/, css/ Frontend assets
api/app/Controller/ CakePHP REST API controllers
api/app/Model/ CakePHP REST API models
scripts/ Perl system management (.in templates)
db/ Schema (zm_create.sql.in) and migrations (zm_update-*.sql)
tests/ Catch2 unit tests + test data
misc/ Server configs (apache, nginx, systemd)
dep/ Vendored deps (catch2, jwt-cpp)
.github/workflows/ CI/CD pipelines
- Create GitHub Issue:
gh issue create --title "..." --label enhancement|bug - Create Feature Branch:
git checkout -b <issue>-<description>from master - Write failing test first (TDD)
- Implement - follow existing patterns, keep changes minimal
- Build & test - must all pass (see Build and Testing sections)
- Commit in logical chunks with conventional messages
- Request user approval - never merge without it
- After approval: merge to master, delete branch, push, verify issue closes
- C++ changes (
src/): Rebuild required, run ctest. Daemons must be restarted to pick up changes. - PHP/JS changes (
web/): No rebuild needed — PHP is interpreted, changes are live immediately. Test in browser + ESLint. - Database schema (
db/): Create migration filezm_update-X.X.X.sqlfor existing installs, also updatedb/zm_create.sql.infor fresh installs. Both must result in the same schema. - Perl scripts (
scripts/): Edit.intemplate files (NOT generated scripts). cmake substitutes@ZM_*@variables to produce the final scripts. When testing perl changes, any perl script will try to read /etc/zm/zm.conf and needs to be run as a user with permission to read it. Best to run with sudo -u www-data - API changes (
web/api/): Clear CakePHP cache (tmp/cache/) if you change models or routes. Test endpoints with curl.
gh issue create --title "Add event favorites" --label enhancement
# Note issue number, e.g. #42
git checkout master && git pull && git checkout -b 42-event-favorites
# Write test first, implement, build & test
cd build && cmake --build . && ctest
npx eslint .
# Commit
git add <files>
git commit -m "feat: add favorites toggle to event view refs #42"
# Ask user for approval, then after approval:
git checkout master && git merge 42-event-favorites
git push origin master
git branch -d 42-event-favorites && git push origin --delete 42-event-favorites# Standard build (out-of-source required)
mkdir build && cd build
cmake ..
cmake --build .
# Debug build with tests
cmake -DCMAKE_BUILD_TYPE=Debug -DBUILD_TEST_SUITE=ON ..
cmake --build .
# Build specific target
cmake --build . --target zmc| Option | Values | Default | Notes |
|---|---|---|---|
CMAKE_BUILD_TYPE |
Release/Debug/Optimised | Release | |
BUILD_TEST_SUITE |
ON/OFF | OFF | Enables Catch2 tests |
ENABLE_WERROR |
ON/OFF | OFF | Warnings as errors (CI uses ON) |
ASAN |
ON/OFF | OFF | AddressSanitizer |
TSAN |
ON/OFF | OFF | ThreadSanitizer (mutually exclusive with ASAN) |
ZM_CRYPTO_BACKEND |
openssl/gnutls | ||
ZM_JWT_BACKEND |
libjwt/jwt_cpp | ||
ZM_TARGET_DISTRO |
FreeBSD/fc/el/OS13 | (Debian) | Platform-specific paths |
ZM_ONVIF |
ON/OFF | ON | ONVIF camera support |
- Write a failing test that reproduces the issue or validates the feature
- Implement the fix/feature
- Run tests - verify they PASS
- Run full test suite for regressions
- Only then commit
# Build with tests
cmake -DBUILD_TEST_SUITE=ON .. && cmake --build .
# Run all tests
ctest
# Or directly with more output
./tests/tests
# Run specific test / list tests
./tests/tests "[Box]"
./tests/tests --list-tests
./tests/tests "~[notCI]"Test location: tests/ directory
Existing modules: zm_box.cpp, zm_comms.cpp, zm_crypt.cpp, zm_font.cpp, zm_poly.cpp, zm_utils.cpp, zm_vector2.cpp
Framework: Catch2 with custom header tests/zm_catch2.h, main in tests/main.cpp
Test data: tests/data/fonts/
npx eslint . # Lint all
npx eslint --fix web/js/ # Auto-fixESLint config: eslint.config.js (ESLint 9 flat config, Google style guide). Runs in CI via .github/workflows/ci-eslint.yml.
Manual testing required. Test endpoints with curl/browser. Verify JSON responses, auth (session + JWT), error handling, DB persistence.
sudo zmupdate.pl --check # Check for updates
sudo zmupdate.pl # Apply migrationsWhen creating migrations: test upgrade path AND verify fresh install matches migrated schema.
- Unit tests are DISABLED in CI (
BUILD_TEST_SUITE=0) — some tests need hardware/network access. Running locally goes beyond CI requirements but is still expected for local development. - CI does: multi-platform builds (Debian/Ubuntu/CentOS with GnuTLS/OpenSSL + libjwt/jwt_cpp matrix), ESLint, CodeQL security scanning
- C++17 standard, follow existing patterns in file/module
- Memory: RAII, smart pointers where appropriate
- Error handling: Exceptions for exceptional cases, return codes for expected errors
- Logging: Use printf-style
Debug(1, "msg %s", val),Info(...),Warning(...),Error(...),Fatal(...)— NOT iostream style. The number in Debug() is verbosity level (1-9).
- PSR-12 where practical, follow existing conventions
- Security: Validate ALL user input, use prepared statements (NOT string interpolation — legacy code has SQL injection bugs we're fixing), sanitize output with htmlspecialchars/json_encode, CSRF protection on forms
- CakePHP 2.x framework in
web/api/— we're stuck on 2.x due to migration cost, don't try to upgrade it
- Google JavaScript Style Guide (ESLint configured)
- jQuery + Bootstrap (legacy codebase), ES6+ where browser support allows
- Edit
.intemplate files, NOT generated scripts — cmake substitutes variables like@ZM_LOGDIR@to produce the final scripts - Use
ZoneMinder::modules,ZoneMinder::Loggerfor logging - Rerun cmake after editing
.infiles to regenerate the actual scripts
Conventional format with issue references:
feat|fix|test|docs|chore|refactor|perf|style: description refs #N
refs #Nreferences issue;fixes #Ncloses it- Imperative mood ("add feature" not "added feature")
- Be detailed and descriptive, no vague summaries
- One logical change per commit
- No superlative language ("comprehensive", "critical", "major", "massive") — AI agents tend to use these; they make commit logs unreadable
- Split unrelated changes into separate commits — makes bisecting and reverting possible
Before committing or claiming complete:
- Tests written/updated BEFORE or DURING implementation
- Build succeeds:
cmake --build .(C++ changes) - C++ tests pass:
ctestor./tests/tests - JavaScript linting passes:
npx eslint . - Manual testing done (PHP/web changes)
- No compiler warnings or linter errors
- Code follows existing patterns
- Commit messages follow conventional format with issue reference
- Feature is COMPLETE (not half-implemented)
- State which tests were run and their results
NEVER commit if: tests failing, tests missing for new code, build fails, warnings exist, feature incomplete.
# Debug build
cmake -DCMAKE_BUILD_TYPE=Debug .. && cmake --build .
# AddressSanitizer
cmake -DCMAKE_BUILD_TYPE=Debug -DASAN=ON .. && cmake --build .
# Debug logging
export ZM_DBG_LEVEL=9 ZM_DBG_LOG=/tmp/debug.log
./src/zmc -m 1
# Log locations: /var/log/zm/ (zmc_m1.log, zma_m2.log, etc.)
# Runtime log level: kill -USR1 <pid> (increase) / kill -USR2 <pid> (decrease)- Bug reports & features: GitHub Issues (read posting rules first)
- Support: Forums, Slack, Discord
- PRs: Fork -> feature branch -> implement + test -> PR. Maintainers merge after review.
- Docs: https://zoneminder.readthedocs.org (source in
docs/)
Use grepai as PRIMARY tool for code exploration and search.
Use grepai search INSTEAD OF Grep/Glob for:
- Understanding what code does or where functionality lives
- Finding implementations by intent ("authentication logic", "error handling")
- Exploring unfamiliar parts of the codebase
Only use Grep/Glob for exact text matching (variable names, imports, specific strings) or file path patterns.
If grepai fails (not running, index unavailable), fall back to Grep/Glob.
grepai search "user authentication flow" --json --compact
grepai search "JWT token validation" --json --compactgrepai trace callers "HandleRequest" --json
grepai trace callees "ProcessOrder" --json
grepai trace graph "ValidateToken" --depth 3 --jsongrepai searchto find relevant codegrepai traceto understand function relationshipsReadtool to examine files from results- Grep only for exact string searches if needed