Open-source network management and monitoring platform for mixed-vendor environments. A central control plane that manages devices through pluggable vendor adapters — each plugin is a small microservice speaking one common contract, so adding a new device family never means touching the core.
UNM is a management appliance — never expose the web UI to the public internet. It holds credentials for every device it manages and can write their configuration. Keep it reachable only via VPN, or behind a reverse proxy with additional authentication (TLS termination + auth). Security has layers: even with best practices in place, direct exposure of an admin plane is how networks get owned.
Also note: UNM is not affiliated with any device vendor — see Trademark disclaimer at the bottom.
- Features
- Architecture
- Repository layout
- Getting started
- Security model
- Device addressing (multi-IP)
- Plugin model
- Plans & configuration jobs
- Backup & replication
- MCP server (AI agent access)
- Development workflow
- Scaling & HA
- Open API — vendor-neutral REST contract in
openapi/unified-network-manager.openapi.yaml; the Express API validates every request against it at runtime (express-openapi-validator). - Plugin system — vendor adapters are independent microservices discovered via mDNS/DNS-SD with an HTTP polling fallback. A device can carry multiple plugins at once (e.g. generic SNMP + a vendor plugin), each with its own credentials; exactly one is primary.
- Authentication with RBAC + 2FA — JWT auth, flat permission catalog, DB-backed roles, TOTP two-factor for privileged operations (config jobs and plan execution require an enrolled authenticator). The API itself is a pure auth broker: credential verification is delegated to the pluggable basic-auth identity provider.
- Secrets management — device credentials are encrypted at rest by the pluggable secrets-provider plugin (the crypto holder); the API only ever stores ciphertext. Generatable secret types (WireGuard key pairs, tunnel PSKs, OpenVPN CA/cert/key) can be created and regenerated from the UI or as part of a plan.
- Configuration workflows — single-device config jobs plus multi-step plans: ordered steps with dependencies, token substitution between step results (
${steps[N].result.details.<key>}), automatic best-effort config snapshots before writes (undoable plans), and secret provisioning wired into the plan lifecycle. - Cross-device inventory pages — Networks (logical subnets/VLANs merged across devices), Hosts (one row per MAC, ARP/NDP/DHCP/Wi-Fi sources), Routes (static + connected + dynamic, with gateway health enrichment), Tunnels (WireGuard/IPsec/OpenVPN live state: peer addresses, handshake age, transfer rates), and Wi-Fi clients.
- Plugin log pipeline — plugins ship structured logs to the API (
POST /plugin/logs); entries are retained for a configurable number of days and browsable globally or per device in the webapp (gated by its ownlogs:readpermission). Emission is on by default and can be switched off globally or per device. - Plugin self-metrics — every plugin auto-serves
GET /metrics(CPU%, heap/RSS memory, event-loop lag, in-flight polls, last-poll duration + error rate, emitted metric count, log queue depth); the API samples it every 30 s and the webapp shows per-plugin runtime health with history sparklines — for spotting overloaded plugins or growing telemetry payloads. - MCP server — exposes the whole API to AI agents over the Model Context Protocol; new endpoints appear as tools automatically.
flowchart LR
subgraph Browser
W[Webapp<br/>React + Vite]
end
subgraph Docker
A[API :3000/v1<br/>Express + Drizzle + Postgres 16]
P1[Generic SNMP plugin]
P2[ICMP plugin]
P3["Generic HTTP(S) / NTP plugins"]
P4[Generic SSH plugin]
BA[basic-auth plugin<br/>identity provider]
SP[secrets-provider plugin<br/>crypto holder]
M[MCP server :3901]
end
D[(Postgres 16)]
HW[Real appliances /<br/>mock-appliance]
W -->|REST + JWT| A
M -->|OpenAPI tools| A
A <--> D
A -->|/monitoring, /config, /plan<br/>+ x-unm-settings header| P1 & P2 & P3 & P4
P1 & P2 & P3 & P4 -->|telemetry + logs| A
A -->|credential verification| BA
A -->|encrypt/decrypt/generate| SP
P1 & P3 & P4 --> HW
- Control plane API (
api/) — auth, RBAC, inventory, monitoring, config jobs, plans, secrets broker. Background workers (telemetry poll every 30 s, config-job executor, plan executor, log retention sweep) start only after the HTTP server has bound successfully. - Plugin runtime — each plugin advertises itself via
/discover(identity, capabilities, options, actions, device-model catalog). The API caches descriptors and injects effective settings as anx-unm-settingsJSON header on every call it makes to a plugin. - Telemetry pipeline — normalized metrics + inventory ride in one
telemetryrow per poll; the API fans inventory out into the cross-devicenetworks,hosts,routes, and tunnel tables.
| Path | What it is |
|---|---|
api/ |
Express API service (Drizzle ORM + Postgres), OpenAPI-validated, JWT/RBAC/TOTP auth, workers for telemetry/config jobs/plans/log retention |
webapp/ |
React 18 + Vite + Tailwind SPA (login, devices, plans, networks, hosts, routes, tunnels, Wi-Fi, logs, plugins, secrets, sites, users, roles, tokens) |
packages/plugin-sdk/ |
Shared TypeScript SDK for plugins (@unm/plugin-sdk): HTTP server scaffolding (incl. auto-mounted self-metrics /metrics), settings reading, API client, structured logger |
packages/snmp-client/ |
Shared net-snmp CLI wrapper used by the SNMP + LANCOM plugins |
plugins/ |
One microservice per vendor adapter — 20 plugins in total. The full list with supported vendors & devices lives in docs/PLUGINS.md; _template/ is the scaffold for new plugins |
plugins/basic-auth/ |
Identity provider plugin — local users + TOTP verification on behalf of the API (swappable via AUTH_PLUGIN_ID) |
plugins/secrets-provider/ |
Secrets provider plugin — encryption at rest (owns MASTER_KEY) + key/cert generation (swappable via SECRETS_PLUGIN_ID) |
mcp/ |
MCP server exposing the OpenAPI operations as tools (+ read-only SQL & plugin probes on a gated debug endpoint) |
openapi/ |
The OpenAPI contract — single source of truth for the public API surface |
scripts/ |
Mock OPNsense appliance + TLS material, plan e2e test suite |
docs/PLUGINS.md |
Plugin catalog — every shipped plugin with its supported vendors & devices |
docs/PLUGIN-DEVELOPMENT.md |
The plugin developer guide — contract, SDK usage, settings loop, logging, discovery, deployment |
Prerequisites: Docker (with compose). That's it — every component ships as a pre-built image on the GitHub Container Registry, so running UNM needs no Node.js and no local build.
git clone https://github.com/MichaelKirgus/UnifiedNetworkManager.git
cd UnifiedNetworkManager
docker compose up -dThe default docker-compose.yml is the end-user deployment: same services, env vars and volumes as the dev file — every locally-built service is pulled from GHCR instead. Overrides (.env or environment):
| Variable | Default | Effect |
|---|---|---|
UNM_IMAGE_TAG |
dev |
Image tag to pull — use a release tag like v0.1.0 for stable deployments, or nightly for the latest pre-release build |
UNM_REGISTRY |
ghcr.io/michaelkirgus/unifiednetworkmanager |
Registry prefix (self-mirroring) |
UNM_WEB_PORT |
8080 |
Host port the webapp is published on |
The MCP server is not exposed to the host by default — it carries an API token with full agent capabilities, so publishing it on all interfaces is a deliberate choice. If you need remote MCP access, add your own mapping (e.g. UNM_MCP_PORT=8081) behind auth or a reverse proxy.
For a production deployment, copy the annotated template to .env and change at least the values marked MUST CHANGE (MASTER_KEY, CRYPTO_SALT, POSTGRES_PASSWORD, PLUGIN_API_KEY) — they are insecure dev defaults:
cp .env.example .env
# edit .env → set a strong MASTER_KEY (openssl rand -hex 32), CRYPTO_SALT, POSTGRES_PASSWORD, PLUGIN_API_KEY
docker compose up -dTear down with docker compose down (add -v to also remove the Postgres volume).
On a fresh deployment the API seeds an admin user (username admin, password admin). Sign in at http://localhost:8080 — no .env file is needed, every variable has a sane default.
The first sign-in is password-only: you are then redirected into a mandatory setup wizard that forces you to
- change the initial password, and
- enroll a second factor (TOTP authenticator or passkey)
before you can use anything else. There is no skip — once the wizard completes, 2FA is required for every subsequent login of that account.
If you're working on UNM itself, use setup.sh instead — it needs Node.js ≥ 20 and builds everything from source (it works against docker-compose.dev.yml, which carries the build: blocks). The script detects whether you can reach the Docker daemon without sudo and only escalates when truly needed.
./setup.shThis installs all dependencies (make setup), starts Postgres, syncs the schema (drizzle-kit push), builds the shared packages + plugins in parallel (make build), starts every plugin container with --build, then runs the API and webapp as hot-reloading dev servers. It blocks until you press Enter; teardown (dev servers + containers) happens automatically on exit/Ctrl+C.
Other targets: make up|down|restart, make stop-api|stop-web, make clean (removes containers and volumes).
On first boot the API seeds an admin user (password admin) — see First login: the initial sign-in is password-only and a mandatory setup wizard then forces a new password + second-factor enrollment. Accounts without an enrolled second factor cannot pass 2FA at all, so they are locked out of 2FA-gated endpoints (config jobs, plan execution) until one is enrolled.
Every component is published to the GitHub Container Registry after the plugin contract suite passes. All packages are set to public visibility on GHCR, so pulling needs no docker login. The standard docker-compose.yml (end-user deployment, no build step) is generated from docker-compose.dev.yml by scripts/generate-registry-compose.cjs — change the dev file and regenerate; never edit the generated one by hand. Image names follow ghcr.io/michaelkirgus/unifiednetworkmanager/<service>, where <service> is the compose service name (api, webapp, mcp, unm-ca, and every *-plugin):
docker pull ghcr.io/michaelkirgus/unifiednetworkmanager/api:devTag scheme: branch pushes are tagged with the branch name (main → dev), and release tags (v0.1.0) additionally move latest. So latest always points at the newest stable release, never at dev-branch builds — pin :latest for stable deployments or a version tag to be explicit.
Two automated lanes sit on top of that (see .github/workflows/docker-publish.yml):
- Stable releases — pushing a strict semver tag publishes all images tagged with it and creates the matching GitHub Release (after the plugin contract suite passes):
git tag v0.2.0 && git push origin v0.2.0 - Nightly pre-releases — a daily scheduled build of
main(or manual dispatch) publishes images taggednightly-<YYYYMMDD>plus rollingnightly, and creates a dated GitHub pre-release. Pre-releases never movelatest.
- RBAC — flat permission catalog (
api/src/auth/permissions.tsis the single source of truth;GET /v1/permissionsserves it to the UIs). Users carry their effective permissions in JWT claims; roles are DB-backed bundles unioned into explicit grants at login time. New permissions added to the catalog are backfilled onto existing admin users and the built-in admin role on API start, so upgrades never lock admins out. - 2FA (TOTP) — required for privileged operations (
configs:apply); enrollment is per-user via the My Account page or by an admin withusers:manage-credentials. - Secrets — values are encrypted by the secrets-provider plugin; the API stores only ciphertext and never sees plaintext. The webapp shows metadata only.
- SNMP transport — SNMP queries run in-process over native UDP (
@unm/snmp-clienton thenet-snmpnpm package); credentials (community strings, USM passphrases) are never passed as command-line arguments or written to logs. DES privacy is rejected at credential-validation time (Node ≥17 OpenSSL dropped it) — use AES. - API tokens — long-lived bearer tokens (
unm_…) for agents/scripts, self-service on the Tokens page orPOST /v1/tokens, with per-token permission narrowing.
Permission catalog (current):
| Permission | Grants |
|---|---|
devices:read |
View devices, their plugins and telemetry |
logs:read |
View plugin log entries (device and global) — separate from devices:read because logs can carry critical details |
configs:apply |
Submit configuration jobs / execute plans (requires 2FA) |
plugins:manage |
Change global plugin settings |
secrets:read / secrets:write / secrets:delete |
Manage secrets (metadata only — values stay encrypted) |
sites:write |
Create, update and delete sites |
tokens:manage |
Create, view and revoke your own API tokens |
users:read / users:write / users:create |
View users; edit roles/permissions & manage roles; create users with initial password + TOTP enrollment |
users:manage-credentials / users:self-credentials |
Reset passwords and manage 2FA of other users / own account |
UNM is a management appliance. The web UI must never, ever be made available on the public internet.
It authenticates as admin to every device it manages and can rewrite their configuration (firewalls included). Exposing it directly means handing an attacker your entire network's keys.
Only expose it via:
- a VPN (WireGuard/IPsec/OpenVPN — UNM itself can plan those for you), or
- a reverse proxy with additional authentication in front (TLS termination + auth, e.g. an SSO/OAuth proxy or mutual TLS), ideally combined with source-IP allow-listing.
Security has layers: even if every best practice above is applied, each layer only matters while the others hold. A single vulnerability — in UNM, its dependencies, or a misconfigured proxy — becomes fatal when the admin plane sits directly on the internet. Treat "reachable from anywhere" as an incident, not a convenience.
Each device has exactly one primary address (managementAddress) plus any number of secondary IPs. All addresses must be a bare IPv4 literal, an IPv6 literal, or a DNS name — no scheme (https://…), because choosing the transport is each plugin's own decision.
- The primary is set at creation and edited via
PATCH /devices/{id}; secondaries are managed withGET/POST /devices/{id}/ipsandDELETE /devices/{id}/ips/{ipId}(mirrors the plugin-assignment pattern). - Every
/monitoringand/configcall to a plugin carries all addresses asipAddresses: [{ address, isPrimary }], so plugins can reach secondary interfaces when they need to. - Transport handling (TLS vs plaintext, self-signed certificates) lives in the plugin, not on the device — e.g. the OPNsense plugin's
transportoption (auto= HTTPS with self-signed accepted,https, orhttp).
Each plugin is responsible for one or more vendor/deviceType combinations and advertises capabilities:
monitoring(health/metrics/events)inventory(facts/interfaces/modules — propagated into the cross-device pages)configuration(read/apply/reconcile actions)planning(multi-step, multi-device plan intents with a step catalog)secretsProvider/identityProvider(the two broker plugins; swappable viaSECRETS_PLUGIN_ID/AUTH_PLUGIN_ID)
Plugins are discovered automatically: every plugin SELF-REGISTERS with the API on boot (POST /v1/plugin/register, SDK startRegistration) using its UNM_API_URL + PLUGIN_API_KEY, and mDNS/DNS-SD is a second path where multicast reaches. UNM keeps no catalog of known plugins — deploying a third-party plugin needs zero changes to the main app (see docs/PLUGIN-DEVELOPMENT.md §1). The full developer guide, including the settings loop, credential slots, device-model catalogs, and the logging pipeline, lives in that document.
Every plugin ships structured logs to POST /plugin/logs (shared x-plugin-api-key, self-reported pluginId). Entries land in plugin_logs with level, message, optional JSON context and device association; a retention worker deletes rows older than LOG_RETENTION_DAYS (default 7) hourly. The webapp shows them on the global Logs page and per device in Device Detail — both gated by logs:read.
Emission is on by default via the standard settings loop: each plugin declares a boolean logToApi option, so it can be switched off globally (Plugins → Configure) or overridden per device without redeploying anything. The SDK's createPluginLogger() batches entries (2 s flush), queues up to 500 with drop-oldest backpressure, and degrades silently when the API is unreachable — logging never blocks a poll cycle.
Every plugin supports running multiple instances of the same type — e.g. one ICMP or SNMP plugin at each remote site, all reporting into a single UNM instance. Each instance registers as its own plugin row and can be assigned to devices independently.
Two optional environment variables control this:
| Variable | Effect |
|---|---|
PLUGIN_INSTANCE_ID (e.g. site-a) |
Makes the advertised identity unique (<plugin-uuid>:site-a). Without it, two instances of the same plugin type would collide into one registration. |
PLUGIN_DISPLAY_NAME (optional) |
Overrides the displayed name. If unset, the instance id is appended automatically: ICMP Plugin (site-a). |
With neither set, behavior is exactly as before — single-instance deployments are unaffected. A commented example lives in docker-compose.yml. Notes: each instance needs a unique ADVERTISED_ENDPOINT (and self-registers independently on boot); settings are per-instance; changing an id/name just restarts it and the old registration goes offline like any stopped plugin.
- Config jobs — single-device action submissions (
POST /devices/:deviceId/config-jobswith an explicitpluginId), executed by the config-job worker against that plugin assignment. Requireconfigs:apply+ 2FA. - Plans — multi-step workflows with per-step dependencies, reordering (cycle-checked while draft/ready), and
${steps[N].result.details.<key>}token substitution from earlier step results. The executor runs one plan at a time, persists the resolved input of each step before dispatch, honorsbestEffortflags, and marks orphaned executing plans failed on startup. Plan creation can provision secrets (e.g. WireGuard key pairs) through the secrets provider with rollback on failure; provisioned material is referenced by id in steps and never stored in plaintext anywhere. - E2E test suite:
scripts/e2e-plans.tsagainst the mock OPNsense appliance (docker compose --profile test up -d mock-appliance; the service is hidden behind thetestprofile so productiveups never start it).
The Backups page (gated on the instance:backup permission) manages UNM's own database — not device configs:
- Backups are section-based gzip-compressed JSON archives (
.unmbak.gz). Sections:identity(users/roles/policies),settings,devices(+plugins/secrets),documented(planned networks/hosts),plans, andliveInventory(regenerable telemetry — only in the All preset). Presets bundle sections; any combination works. Deliberately excluded from every backup: API tokens, passkeys, live telemetry/logs/events and the plugin registry cache. - Destinations are per-backup: the local volume (
unm-backups→/data/backups, override withUNM_BACKUP_VOLUME) or a registered file-repo instance (bytes content-addressed there; the backup is bound to exactly one instance — no fallback to a sibling). - Restore is a safe merge: rows are upserted by primary key, and rows absent from the archive are never deleted. The webapp walks upload/stored → manifest preview → per-section report.
- Replication syncs portable config (everything except
liveInventory) one-way to/from a peer instance using a peer API token that carriesinstance:backupthere. Targets support manual Sync now and an optional interval (leader-gated scheduler). A default exclusion list strips stack-dependent settings (instanceName, update feed, provider bindings, …) before sending/applying — editable per target.
Cross-instance secret portability: device secrets are encrypted with a key derived from MASTER_KEY + CRYPTO_SALT. For replicated/restored secrets to decrypt on the peer, both instances MUST run with identical values for both variables (set them in each stack's .env).
Dev → prod recipe: 1) create an API token on prod with only instance:backup (Tokens page); 2) on dev, Backups → Replication → Add target (push, that URL + token, optional interval); 3) Sync now, then watch the scheduler. Verify instanceName/update-feed settings were NOT overwritten on prod.
The mcp/ service exposes the UNM API to AI agents via the Model Context Protocol. It reads the OpenAPI spec at startup and turns every operation into a tool — new API endpoints appear automatically. The container serves two Streamable-HTTP endpoints on port 3901:
| Endpoint | Purpose |
|---|---|
POST /mcp |
Production-safe: OpenAPI operations only. What an agent can do is controlled entirely by the permissions of its API token — create a scoped token (e.g. read-only) on the Tokens page and pass it as UNM_API_TOKEN. |
POST /debug/mcp |
Extended debug for diagnosing UNM itself: adds query_database (single read-only SELECT/WITH statement, max 200 rows) and plugin_probe (live /healthz + /discover of every plugin endpoint). Requires Authorization: Bearer <MCP_DEBUG_KEY>. |
Environment variables:
| Variable | Effect |
|---|---|
UNM_API_TOKEN |
Long-lived API token for the OpenAPI tools — required. Create it on the Tokens page (or POST /v1/tokens). |
UNM_API_BASE |
Base URL of the API. Defaults to http://localhost:3000/v1; the container uses http://api:3000/v1. |
MCP_DEBUG_KEY |
Bearer key gating /debug/mcp (default in compose: dev-mcp-debug-key). |
MCP_KEY |
Optional — when set, also gates the plain /mcp endpoint. |
DATABASE_URL |
Postgres connection used by the debug SQL tool only (same DB as the API). |
VS Code MCP client config (remote/HTTP transport):
The same server also runs in stdio mode without Docker (client-launched, production-safe tools only): node mcp/dist/index.js with UNM_API_TOKEN set — no MCP_PORT needed.
- Build order matters:
@unm/plugin-sdkand@unm/snmp-clientmust build first — npmfile:dependencies are copied into each consumer'snode_modules, somake buildrefreshes those copies before fanning out all plugin builds in parallel. After editing a shared package, always runmake build. - Type-check:
cd api && npx tsc --noEmit, same forwebapp/and each plugin; or justmake build. - OpenAPI is enforced: every new public route must be added to
openapi/unified-network-manager.openapi.yaml— the validator 404s undocumented paths. Structural check:node validate-openapi.cjs. - Schema changes: idempotent DDL in
initDb()(api/src/db.ts) +drizzle-kit push; keepschema.tscomposite-PK names equal to the live Postgres constraint names. - Smoke tests:
cd plugins/opnsense && ../../node_modules/.bin/tsx smoke-test.ts(same forplugins/mikrotik-swos). - Plugin images always rebuild from source (
docker compose up -d --build <service>) — reusing stale images silently serves old code.
| Variable | Default | Effect |
|---|---|---|
JWT_SECRET |
dev-secret |
JWT signing key — change in production |
DATABASE_URL |
postgresql://unm:unm@postgres:5432/unm |
Postgres connection |
PLUGIN_API_KEY |
dev-plugin-key |
Shared key the API presents to plugins (x-plugin-api-key) and that plugins use for log shipping / broker calls |
AUTH_PLUGIN_ID |
built-in basic-auth plugin | Active identity provider; falls back to built-in when unset/offline |
SECRETS_PLUGIN_ID |
built-in secrets-provider plugin | Active secrets provider; same fallback behavior |
LOG_RETENTION_DAYS |
7 |
Plugin log + self-metrics retention window for the hourly sweep |
TELEMETRY_RETENTION_DAYS |
7 |
Raw telemetry snapshot retention — older rows are deleted by the hourly retention sweep (the fastest-growing table in the system) |
TELEMETRY_POLL_INTERVAL_MS |
30000 |
Telemetry poll beat. Raise for very large fleets, or lower TELEMETRY_MAX_CONCURRENCY instead |
TELEMETRY_MAX_CONCURRENCY |
64 |
Max in-flight /monitoring calls per poll cycle — keeps the per-beat burst flat regardless of fleet size |
SECRET_CACHE_TTL_MS |
60000 |
How long decrypted secrets are cached for the poll path (write paths always read fresh). Cuts secrets-provider load by orders of magnitude at scale; set 0 to disable |
DATABASE_POOL_MAX |
20 |
Postgres pool size — raise if you see queued writes during poll beats on large fleets |
PLUGIN_POLL_ENDPOINTS |
(empty) | Optional legacy discovery fallback: comma-separated endpoints to poll when a plugin cannot reach the API at all. Normal deployments leave this unset — plugins self-register instead (see §Plugins) |
PLUGIN_HOST_PORT_MAP |
(unset) | Dev-mode only (setup.sh sets it): service:port,… pairs letting the host-running dev API rewrite compose service names to localhost ports |
The API is designed to run as N replicas behind a load balancer while exactly one of them runs the background workers. Election uses Postgres advisory locks (api/src/workers/leader.ts) — no Redis, no external coordinator:
- Each worker (telemetry polling, plan execution, log/telemetry retention, plugin self-metrics, plugin discovery) takes a non-blocking
pg_try_advisory_lockat startup. The winner runs the loop; the others log waiting for leadership and serve HTTP only. - Failover is automatic: if the leader dies (crash, OOM,
docker compose down), Postgres frees its locks and every standby re-attempts within 15 s. A rolling deploy steps down gracefully on SIGTERM so handover takes seconds. - All routes are stateless — jobs land in Postgres (
config_jobs,plans), not process memory. The one exception is the config-job worker, which drains each instance's own in-process queue, so every replica runs it (it only picks up rows its routes enqueued).
Overhead of HA-by-design on a single-replica deployment: one extra pg_try_advisory_lock per worker at startup plus one verification query per minute — effectively zero. The lock rides on one dedicated pinned pool connection that is never returned to the idle list (node-postgres would destroy it after 5 min and silently drop session-scoped locks).
Capacity guidance (2 plugins/device, ~10 KB snapshots):
| Fleet | Notes |
|---|---|
| ≤ 50 devices | Defaults are fine. Retention is the only thing that matters long-term. |
| 50–150 devices | Fine as-is; watch plugin self-metrics (inFlight, event-loop lag) during poll beats. |
| 150–400+ devices | Raise TELEMETRY_POLL_INTERVAL_MS (60 s) and/or keep the concurrency cap; consider a second API replica for HA — polling load stays on one leader, HTTP scales out. |
The normalized inventory tables (networks, hosts, …) hold only current state and are swept at 24 h independently of telemetry retention.
UnifiedNetworkManager (UNM) is an independent, open-source project. It has no connection to, affiliation with, or endorsement by any vendor whose devices it supports — including but not limited to OPNsense, MikroTik, LANCOM Systems, TrueNAS/iXsystems, FRITZ!Box (AVM), OpenWrt, Zabbix and Mailcow. All product names, trademarks and brands mentioned in this repository are the property of their respective owners and are used solely to identify compatible hardware and software — not to imply any partnership, sponsorship or official status.
{ "mcpServers": { "unm": { "url": "http://localhost:3901/mcp" }, "unm-debug": { "url": "http://localhost:3901/debug/mcp", "headers": { "Authorization": "Bearer dev-mcp-debug-key" } } } }