v0.1.1 on Docker Hub · one page, one connector · ~1/5 of the plan built

Documentation that corrects itself.

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.

★ View on GitHub How it works Rust · MariaDB · UniFi-first · AGPL-3.0

Observed · what's really there

device on .53seen 2 min ago
unknown MACnew
two devices, one IPconflict
the gap is the product linked, never merged

Declared · what you wrote down

.53 freedocumented
host unseen 30dstale?
owner of .1which device?

The core idea

Every tool shows you one half. opencmdb holds both.

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.

It reconciles

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.

It abstains honestly

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.

It never moralises

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

The reconciliation engine you can actually run.

  • Reconciliation, unpaywalled. The observed-vs-declared loop is a top-of-market paid feature elsewhere. Here it is the whole point, free and self-hosted.
  • Radically smaller footprint. One binary + your MariaDB. No Redis, no workers, no queue, no reverse-proxy tax — two moving parts against the incumbents' five.
  • UniFi-first, zero privilege. Reads clients, switch ports, SSIDs, VLANs and DHCP leases over plain outbound HTTP — no network privilege required.
  • For the operator who runs their own infrastructure — the advanced home lab, the small business without a dedicated IT team — not the enterprise IPAM desk.

Scope, phased on purpose

What it will do.

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 VMsMVP
IPAM: per-subnet occupancy, find a free IP, which device owns a conflicting addressMVP
First-class UniFi connector (zero privilege) + generic ARP/ping scannerMVP
Honest source state — blind vs merely reduced, never conflatedMVP
Software & applications, owner and criticality; "Hosted here" on a device recordMVP
Alerts (unknown device · stale IP · IP conflict with identification) → in-app + webhookMVP
Read-only JSON API + authenticated Prometheus /metrics; bilingual EN/FR UIMVP
Blast-radius / impact view, interactive topology map, more connectors (Omada, Mikrotik, pfSense/OPNsense, hypervisors)Growth
A stable, versioned public APIVision

Design principles

Load-bearing, and enforced in code.

Composite identity, not a raw MAC

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.

Linked, never merged

Declared and observed data are joined by a link; neither overwrites the other. Documenting a value is a deliberate, field-by-field act.

A source that goes blind cannot lie

A faulted source can only remove knowledge, never invent it. Observation-derived alerts are suppressed rather than fabricated.

No setting decides identity in SQL

Comparison and normalisation happen in application code, never in the database — so a collation can never silently settle a question of identity.

Architecture

A workspace with a deliberate frontier.

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).

Stack

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.

One database, on purpose

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

Honest about its own state.

opencmdb's thesis is that documentation must not lie about reality. This page holds itself to the same rule.

There is a running binary, and it is a walking skeleton. v0.1.1 is tagged and published to Docker Hub. It starts, connects to MariaDB, scans a subnet you give it, and serves one page showing a real observed-vs-declared gap — reconciled from genuinely ingested observations, abstaining visibly where it cannot conclude. It has been deployed to a real Synology NAS, and everything that broke on the way is in the issue tracker and fixed.

What it is not, yet. No triage inbox, no IPAM, no UniFi connector, no alerts, no admin UI — the scan perimeter is still an environment variable. Roughly a fifth of the planned work is done: epics 1–3 of 23 are complete and epic 4 is under way. No upgrade path is promised between 0.x tags, and the schema will move. If you deploy this tonight you will see the idea working end to end on a small perimeter, not a tool you can run your network on.

Run it

# two services: the binary and MariaDB — never a single docker run git clone https://github.com/guycorbaz/opencmdb.git cd opencmdb cp docker/.env.example docker/.env # set DATABASE_PASSWORD, OPENCMDB_SCAN_CIDR, … docker compose -f docker/docker-compose.yml up -d

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.