A self-hosted, single-binary reconciliation engine that continuously compares your network's observed state against the declared state you documented — and turns the gap between them into something you can act on.
The core idea
Your controller and your scanner see the network as it is — live, but undocumented. Your spreadsheet and your IPAM hold what you meant — documented, but stale the moment reality moves. opencmdb keeps both side by side, links them, and never merges them. Every disagreement becomes a gap you resolve one gesture at a time.
An unknown device appeared; a documented IP went quiet; two hosts fight over one address. Each becomes a triage-inbox item — with the one detail your controller's alarm omits: which device.
When the engine genuinely cannot identify something, it says so, visibly, and counts it — grouped by cause — rather than guessing. A false merge is catastrophic and has no clean undo; the engine is built to never merge on doubt.
A wall of gaps on first commissioning is normal, not a reproach. You decide what to document and what to accept — and the tool remembers, so effort falls week over week.
Why it exists
Scope, phased on purpose
The MVP is the reconciliation core, landed correct. Richer surfaces are deliberately deferred so the foundation is right before it is decorated.
| Observed-vs-declared reconciliation with a triage inbox (document · accept-gap · attach · exclude · snooze) | MVP |
| Composite device identity — survives MAC randomisation, multi-NIC hosts, shared-hardware VMs | MVP |
| IPAM: per-subnet occupancy, find a free IP, which device owns a conflicting address | MVP |
| First-class UniFi connector (zero privilege) + generic ARP/ping scanner | MVP |
| Honest source state — blind vs merely reduced, never conflated | MVP |
| Software & applications, owner and criticality; "Hosted here" on a device record | MVP |
| Alerts (unknown device · stale IP · IP conflict with identification) → in-app + webhook | MVP |
Read-only JSON API + authenticated Prometheus /metrics; bilingual EN/FR UI | MVP |
| Blast-radius / impact view, interactive topology map, more connectors (Omada, Mikrotik, pfSense/OPNsense, hypervisors) | Growth |
| A stable, versioned public API | Vision |
Design principles
A MAC identifies an interface; a device is a composite. Uniqueness is a decision the engine makes, not a database constraint — if it could be expressed in DDL, we misunderstood the problem.
Declared and observed data are joined by a link; neither overwrites the other. Documenting a value is a deliberate, field-by-field act.
A faulted source can only remove knowledge, never invent it. Observation-derived alerts are suppressed rather than fabricated.
Comparison and normalisation happen in application code, never in the database — so a collation can never silently settle a question of identity.
Architecture
The domain crate knows nothing of the outside world. An error inside it is domain data, not a string — and the boundary is a gate, not a guideline.
opencmdb/ ├── crates/ │ ├── opencmdb-core/ # the domain: identity, the verdict algebra, the gap. │ │ # no anyhow · axum · sqlx · askama — by rule. │ └── opencmdb-bin/ # everything that touches the outside world: │ # SQL, HTTP, HTML, files, the clock, secrets. ├── xtask/ # the dev-tool runner; a dependency of nobody. └── _bmad-output/ # the complete planning record (D1–D66).
Rust (edition 2024) · axum · askama templates · HTMX (committed, never a CDN) · Tailwind (standalone CLI, no Node) · sqlx · tokio. Server-rendered, polling not SSE at MVP — no SPA framework.
MariaDB 10.11+ only — Synology ships it natively and backs it up. SQLite and MySQL are not supported; PostgreSQL is out at MVP. A single engine keeps the budget of unknowns spent on reconciliation, not portability.
Where it stands
opencmdb's thesis is that documentation must not lie about reality. This page holds itself to the same rule.
Run it
Two traps found the hard way on a real NAS: do not use network_mode: host (the ping scan then fails silently), and a $ in a password inside .env is truncated by Compose interpolation.