Metadata-Version: 2.4
Name: netbox-checkmk-sync
Version: 0.1.0
Summary: NetBox plugin that creates, changes and deletes hosts in Checkmk from NetBox
Author-email: Christian Wirtz <doc@snowheaven.de>
License-Expression: Apache-2.0
Project-URL: Homepage, https://gitlab.thatsallmine.de/checkmk1/netbox_checkmk_sync
Project-URL: Source, https://gitlab.thatsallmine.de/checkmk1/netbox_checkmk_sync
Project-URL: Changelog, https://gitlab.thatsallmine.de/checkmk1/netbox_checkmk_sync/-/blob/main/CHANGELOG.md
Keywords: netbox,netbox-plugin,checkmk,monitoring
Classifier: Development Status :: 4 - Beta
Classifier: Environment :: Web Environment
Classifier: Framework :: Django
Classifier: Intended Audience :: System Administrators
Classifier: Natural Language :: English
Classifier: Natural Language :: German
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Topic :: System :: Monitoring
Classifier: Topic :: System :: Networking :: Monitoring
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
License-File: NOTICE
Requires-Dist: cryptography
Requires-Dist: dulwich
Requires-Dist: PyYAML
Requires-Dist: requests
Dynamic: license-file

# netbox_checkmk_sync

Deutsch: [README.de.md](https://gitlab.thatsallmine.de/checkmk1/netbox_checkmk_sync/-/blob/main/README.de.md)

> **Beta.** `0.1.0` is the first public release. It is covered by unit and end-to-end tests
> but has not run on a production installation yet: try it on a test Checkmk site first.
> Changes to the data model come with migrations; behavior and interfaces may still change
> between `0.x` releases, see [CHANGELOG.md](https://gitlab.thatsallmine.de/checkmk1/netbox_checkmk_sync/-/blob/main/CHANGELOG.md).

A [NetBox](https://netbox.dev) plugin that **creates, changes and deletes hosts in one or
more [Checkmk](https://checkmk.com) instances from NetBox**, with pending changes, preview,
limits, approval and revisions. NetBox stays the source of truth; Checkmk is only written
through its REST API.

**It runs alongside the existing configuration of a Checkmk site.** It only touches the
hosts and objects it created itself (or explicitly adopted); everything configured by hand
stays as it is. It is as easy to add as to remove, so monitoring can move over to NetBox
step by step. See [Alongside an existing configuration](#alongside-an-existing-configuration).

**Status:** beta, version `0.1.0`. All functions are implemented and
covered by unit and end-to-end tests (see [Tested with](#tested-with)); it has not run on a
production installation yet. See [Known limitations](#known-limitations).

Companion plugin for the monitoring status view:
[netbox-checkmk](https://gitlab.com/checkmk1/netbox_checkmk_plugin). Both run independently;
when both are installed, they detect each other. Monitoring this plugin from Checkmk
(pending changes, deploys, approvals, report findings): the MKP
[checkmk_netbox](https://gitlab.thatsallmine.de/checkmk1/checkmk_netbox) 1.1.0 or later.

## Contents

- [Requirements](#requirements), [Tested with](#tested-with) and [Size and timing](#size-and-timing)
- [Installation](#installation)
- [Quick start: the first deploy](#quick-start-the-first-deploy)
- [What it does](#what-it-does)
- [Guides](#guides)
  - [Daily work: change, preview, deploy](#daily-work-change-preview-deploy)
  - [Approval and four-eyes principle](#approval-and-four-eyes-principle)
  - [Responsibility for a group of hosts](#responsibility-for-a-group-of-hosts)
  - [Teams, contacts and notification rules](#teams-contacts-and-notification-rules)
  - [Own tag groups and standard tags](#own-tag-groups-and-standard-tags)
  - [Labels, folders and the profile](#labels-folders-and-the-profile)
  - [Clusters and parents](#clusters-and-parents)
  - [Distributed monitoring](#distributed-monitoring)
  - [NetBox status, downtimes and the grace period](#netbox-status-downtimes-and-the-grace-period)
  - [Taking over existing hosts (Adopt)](#taking-over-existing-hosts-adopt)
  - [Rollback](#rollback)
  - [Consistency report](#consistency-report)
  - [Notification channels](#notification-channels)
  - [Schedule and automatic mode](#schedule-and-automatic-mode)
  - [Git audit and ZIP export](#git-audit-and-zip-export)
  - [Reservations](#reservations)
- [How it works](#how-it-works), [Configuration](#configuration),
  [Permissions](#permissions)
- [Alongside an existing configuration](#alongside-an-existing-configuration)
- [Removing it](#removing-it)
- [Troubleshooting](#troubleshooting)
- [Known limitations](#known-limitations)
- [Demo](#demo), [Development](#development), [Repository layout](#repository-layout),
  [License](#license)

## Requirements

- NetBox **4.6** or **4.7**, with a running background worker (`rqworker`, systemd unit
  `netbox-rq`): deploys, the schedule and the report run there.
- Checkmk **2.5** or **3.0**, a commercial edition (Pro or Ultimate; tested with Ultimate),
  reachable from NetBox over HTTP(S). The Community Edition is not supported: the setup
  check reports it and deploys are blocked.
- A dedicated **automation user** per Checkmk instance. Its role needs at least
  `wato.use`, `wato.edit`, `wato.hosts`, `wato.edit_hosts`, `wato.manage_hosts`,
  `wato.move_hosts`, `wato.rename_hosts`, `wato.edit_folders`, `wato.manage_folders`,
  `wato.all_folders`, `wato.activate`, and per enabled function: `wato.hosttags` (own tag
  groups), `wato.users`, `wato.groups`, `wato.notifications` (contacts), `wato.rulesets`
  (rules), `action.downtimes` (downtimes), `wato.services` (discovery), `wato.bake_agents`
  (baking). The setup check verifies the role; it needs `wato.users` to read it.

### Tested with

| Checkmk image | NetBox | Date | Result |
|---|---|---|---|
| `checkmk/check-mk-ultimate:2.5.0p18` | `netboxcommunity/netbox:v4.7.4` | 2026-11-05 | OK (integration test 142/142 with netbox-checkmk alongside, after removal 10/10) |
| `checkmk/check-mk-ultimate:2.5.0p18` | `netboxcommunity/netbox:v4.6.10` | 2026-11-05 | OK (integration test 135/135, after removal 9/9) |
| `checkmk/check-mk-ultimate:3.0.0-2026.11.05` (daily) | `netboxcommunity/netbox:v4.7.4` | 2026-11-05 | OK (integration test 141/141 with netbox-checkmk alongside, after removal 10/10) |
| `checkmk/check-mk-ultimate:3.0.0-2026.11.05` (daily) | `netboxcommunity/netbox:v4.6.10` | 2026-11-05 | OK (integration test 134/134, after removal 9/9) |
| `checkmk/check-mk-ultimate:2.5.0p18` | NetBox 4.7.4 classic (tarball, pip) | 2026-11-05 | OK (integration test 134/134, after removal 9/9) |
| `checkmk/check-mk-ultimate:2.5.0p18` | NetBox 4.6.10 classic (tarball, pip) | 2026-11-05 | OK (integration test 134/134, after removal 9/9) |
| `checkmk/check-mk-ultimate:2.5.0p18`, central and remote site | `netboxcommunity/netbox:v4.7.4` | 2026-11-05 | OK (distributed monitoring test 9/9) |
| `checkmk/check-mk-ultimate:2.5.0p18`, 1000 devices | `netboxcommunity/netbox:v4.7.4` | 2026-11-05 | OK (scale test 13/13, times in [Size and timing](#size-and-timing)) |

All tests ran in throwaway Docker containers with example data only
(`scripts/integration_test.sh`); the 95 unit tests pass as well. The flow of the release
has 142 checks with netbox-checkmk running alongside (`COMPANION`, NetBox 4.7.4) and 135
without it (eight interplay checks instead of one "not installed" check); classic NetBox
runs without it. The 3.0, classic, distributed and scale rows ran the same day one commit
earlier, with one check less (the duration shown on the deployment page).
After removal there are 10 checks with netbox-checkmk staying, 9 without. *Classic* is
NetBox installed from its release tarball on plain Debian as the NetBox docs describe, with
this plugin installed and removed exactly as this README says (`NETBOX_KIND=classic`).
*Central and remote site* is the [distributed monitoring](#distributed-monitoring) test
(`DISTRIBUTED=1`), *1000 devices* the scale test (`SCALE=1000`).

The integration test covers every function (deploy, approval, responsibility per host
group, rollback, report, notifications, schedule, tag groups, contacts, parents, cluster
hosts, downtimes, discovery, reservations, adopt, hand-back, Git audit, the security checks,
the supported editions), the interplay with netbox-checkmk and removing
the plugin (also while netbox-checkmk stays), and renders the host, rollback,
revision and report pages. The plugin pages were checked by hand with the demo test checklist on
2026-10-28 (NetBox 4.7.3, Checkmk 2.5.0p16) and again on 2026-11-05 (NetBox 4.7.4, Checkmk
2.5.0p17, all 86 steps). It has not been tried on a
production installation yet.

### Size and timing

The scale test takes 1000 devices through the whole cycle. Measured on 2026-11-05 (Checkmk
Ultimate 2.5.0p18, NetBox 4.7.4, PostgreSQL 17, Redis 7):

| Step | 1000 hosts | Where it runs |
|---|---|---|
| Recompute (pending changes, *Changes* page) | 3 s | web request |
| Preview of all changes | 14 s | web request |
| First deploy (1000 hosts created) | 107 s | background job |
| Activation | 1 s | web request (button) or background job (*Activate after deploy*) |
| Change of 100 hosts: recompute, preview, deploy | 40 s | web request and background job |
| Consistency report | 10 s | web request (button) or background job (schedule) |

Test environment: a CI runner, KVM VM with 9 vCPUs on an Intel Core i7-11850H with turbo
switched off (2.5 GHz), 20 GiB RAM, Docker 29; NetBox, its worker, PostgreSQL, Redis and the
Checkmk site in their own containers on that VM, without CPU limits, while a second
Checkmk container (the screenshot job) ran next to them. A server with turbo or a higher
clock is faster, above all for the preview, which mostly runs on one core.

**Estimate for larger setups** (not measured: the times above scaled linearly, same
machine):

| Step | 2000 hosts | 5000 hosts | 10000 hosts |
|---|---|---|---|
| Recompute | 6 s | 14 s | 28 s |
| Preview of all changes | 28 s | 70 s | 2.3 min |
| First deploy | 3.6 min | 9 min | 18 min |
| Change of 10 % of the hosts | 80 s | 3.3 min | 6.6 min |
| Consistency report | 20 s | 50 s | 1.7 min |

The preview and the *Changes* page run inside the web request: from a few thousand hosts
on they reach the usual timeouts of the WSGI server and the reverse proxy (often 60 to
120 s). Deploys run as a background job; its time limit grows with the plan (see below).
A preview as a background job and an incremental preview that only computes changed
objects are planned after 0.1.0.

**Sizing the NetBox server**

- **CPU:** at least 2 vCPUs, so that the web server and the background worker do not slow
  each other down; fast single cores help more than many cores (preview and recompute
  compute on one core).
- **Background worker:** at least one `rqworker`; deploys, the schedule and the report run
  there one after the other, next to NetBox's own jobs.
- **Timeouts:** the timeout of the WSGI server (gunicorn, or granian in netbox-docker) and
  of the reverse proxy (`proxy_read_timeout` in nginx) above the expected preview time, see
  the table. The time limit of a deploy job is set by the plugin itself: three times the
  expected duration plus two minutes, never below NetBox's `RQ_DEFAULT_TIMEOUT` (default
  300 s) and at most six hours. The expected duration comes from the recent deploys of the
  instance (a fixed base plus a share per operation) and is shown on the deployment page,
  the duration of the last deploy on the instance page. The schedule (automatic mode,
  nightly report) runs with NetBox's `RQ_DEFAULT_TIMEOUT`: raise it on large instances.
- **Memory:** not measured yet; the test VM had plenty (20 GiB for NetBox, Checkmk and a
  second site).
- **Database:** every preview stores its plan; open previews expire after *Previews expire
  after (hours)*, revisions keep the managed state of every changed host.

## Installation

1. **Install the package** into NetBox's virtual environment (from PyPI, or
   `pip install /path/to/netbox_checkmk_sync` from a checkout):

   ```console
   source /opt/netbox/venv/bin/activate
   pip install netbox-checkmk-sync
   ```

   NetBox's `upgrade.sh` creates the virtual environment anew. To keep the plugin across
   NetBox upgrades, also add it to `/opt/netbox/local_requirements.txt` (one line:
   `netbox-checkmk-sync`).

2. **Enable it** in `/opt/netbox/netbox/netbox/configuration.py`:

   ```python
   PLUGINS = ["netbox_checkmk_sync"]
   PLUGINS_CONFIG = {
       "netbox_checkmk_sync": {
           # Part of locked_by.instance_id ("<installation_id>-<prefix>"). Must be unique
           # per NetBox installation that writes to the same Checkmk instance; letters,
           # digits, dash and underscore (other characters become "_").
           "installation_id": "netbox",
       },
   }
   ```

3. **Run the migrations and restart** NetBox and its worker:

   ```console
   cd /opt/netbox/netbox
   python manage.py migrate
   systemctl restart netbox netbox-rq
   ```

   After the migration the plugin creates its custom fields (group *Checkmk*) on devices,
   VMs and clusters, see [Custom fields](#custom-fields). An existing `checkmk_hostname`
   field (e.g. from netbox-checkmk) is reused.

4. **Check:** the menu **Checkmk Sync** appears in NetBox, and **Operations > Jobs** lists
   the system job *Checkmk sync schedule*.

5. **Optional, recommended: the monitoring status in NetBox.** This plugin writes the
   configuration; [netbox-checkmk](https://gitlab.com/checkmk1/netbox_checkmk_plugin) shows
   the live host and service state in a "Checkmk" tab on devices, VMs and clusters. It is a
   separate plugin with its own releases and is not installed with this one:

   ```console
   pip install netbox-checkmk
   ```

   ```python
   PLUGINS = ["netbox_checkmk_sync", "netbox_checkmk"]
   ```

   Give it its own Checkmk automation user that may only read (e.g. role *Guest user*),
   not the user of this plugin. With both installed, the configuration panel of a device
   links to its "Checkmk" tab, and an instance shows the netbox-checkmk site with the same
   URL. Each plugin works without the other and can be removed on its own.

Continue with the [quick start](#quick-start-the-first-deploy).

## Quick start: the first deploy

From a fresh installation to the first hosts in Checkmk. Start with a few devices; the
rest follows step by step.

1. **Create the automation user in Checkmk.**
   - **Setup > Users > Roles & permissions:** clone the role *Administrator*, name it e.g.
     *NetBox sync*, and keep only the permissions listed under
     [Requirements](#requirements).
   - **Setup > Users > Users > Add user:** user name e.g. `netbox-sync`, authentication
     *Automation secret for machine accounts*, role *NetBox sync*. Note the secret.
   - Activate the changes in Checkmk.
2. **Create a mapping profile:** **Checkmk Sync > Mapping profiles > Add**, name e.g.
   *Default*. The defaults are fine to start with (folders `/{prefix}/{site}/{role}`).
3. **Create the instance:** **Checkmk Sync > Instances > Add**.
   - **URL:** the site URL, e.g. `https://checkmk.example.com/mysite`.
   - **Username** and **Secret:** the automation user from step 1.
   - **Default profile:** the profile from step 2. Keep the prefix `nb` unless it is
     taken.
4. **Run the setup check:** on the instance page click **Setup wizard**, then **Check
   now**. Steps 1 to 3 (connection, version and permissions, prefix and namespace) must be
   *done*. Fix what is marked *problem* (see [Troubleshooting](#setup-and-connection)).
   The setup check also reads the monitoring sites; without it, objects show *no
   monitoring site known for this instance*. Each item name links to where it is fixed:
   the instance form, the wizard, or the page in Checkmk (opens in a new tab).

   ![Setup wizard with the state of each step](https://gitlab.thatsallmine.de/checkmk1/netbox_checkmk_sync/-/raw/main/docs/screenshots/wizard.png)

5. **Switch functions on** (optional, can wait): in the wizard step *Functions* tick what
   you need (own tag groups are on by default; contacts, rules, discovery, baking are off)
   and click **Save functions and check**.
6. **Assign objects:** on a few devices or VMs, set the custom field **Checkmk instance**
   to the new instance (**Edit**, or select several in the list and **Edit Selected**).
7. **Look at the pending changes:** instance page > **Changes**. Every assigned object is
   listed with action *create*. Objects with an error (e.g. invalid name, no primary IP)
   are listed with the reason.

   ![Changes: pending changes with action, changed fields and author](https://gitlab.thatsallmine.de/checkmk1/netbox_checkmk_sync/-/raw/main/docs/screenshots/changes.png)

8. **Preview:** click **Preview all changes**. The preview reads Checkmk and shows the
   exact plan (operations, safety gate, conflicts, notes). Nothing is written yet.

   ![Preview of the first deploy: plan, safety gate, notes, not deployable](https://gitlab.thatsallmine.de/checkmk1/netbox_checkmk_sync/-/raw/main/docs/screenshots/first-deploy-preview.png)

9. **Deploy:** tick **I checked the preview of this first deploy** and click **Deploy**.
   The deploy runs as a background job; the page refreshes until it is done.
10. **Activate in Checkmk:** the deployment page offers **Activate in Checkmk** right after
    the deploy (also on the instance page; or activate in Checkmk itself as usual). Activation after a deploy is off by default.
    The offer stays as long as that deploy's changes wait, also when *Activate after
    deploy* is switched on afterwards: the setting acts on later deploys only.
11. **Check the result:** the hosts are in Checkmk in the folder `/nb/<site>/<role>`, with
    labels `nb/...`; in NetBox the device page shows the card **Checkmk configuration**
    with the state *Monitored*, and **Checkmk Sync > Hosts** lists all objects with their
    state.

    ![Card Checkmk configuration on the device](https://gitlab.thatsallmine.de/checkmk1/netbox_checkmk_sync/-/raw/main/docs/screenshots/device-card.png)

    ![The hosts in the Checkmk folder /nb/fra1/server](https://gitlab.thatsallmine.de/checkmk1/netbox_checkmk_sync/-/raw/main/docs/screenshots/checkmk-folder.png)

Then extend the assignment step by step, see
[Daily work](#daily-work-change-preview-deploy).

## What it does

Per assigned device, VM or cluster, one Checkmk host:

| Checkmk | From NetBox |
|---|---|
| Host name | name, or custom field **Checkmk host name**; invalid names are an error with a suggestion, or converted automatically (instance option *Invalid host names*) |
| Folder | profile template, default `/{prefix}/{site}/{role}`; missing folders are created |
| `ipaddress` / `ipv6address` | primary IPv4 / IPv6 (clusters: custom field **Checkmk address** or config context `checkmk.address`) |
| `tag_address_family` | derived from the primary IPs (`ip-v4-only`, `ip-v6-only`, `ip-v4v6`) |
| `alias` | description |
| `site` (monitoring site) | custom field **Checkmk monitoring site**, config context `checkmk.site`, profile, else the central site; checked against the instance |
| Labels | `nb/id`, `nb/type`, `nb/site`, `nb/role`, `nb/platform`, `nb/tenant`, `nb/status`, `nb/cluster`, `nb/cluster_type`, `nb/tag/<slug>:yes`, optionally `nb/service/<name>:yes`, plus free labels from the custom field **Checkmk labels** or the config context `checkmk.labels` (always with the prefix) |
| Own tag groups | `nb_criticality` (prod/test/dev) and `nb_monitoring` (full/basic/ping) to start with, editable under **Tag groups**; value from the custom field `checkmk_tag_<slug>`, a NetBox tag mapping, the config context `checkmk.tags`, else the default; groups derived from the role, platform, site or tenant are possible |
| `agent`, `snmp_ds`, `piggyback` | only if the profile sets them (*Standard tags*, e.g. platform `ios` → `no-agent`, `snmp-v2`) |
| `contactgroups` | the teams of the object's contacts (and of the custom field **Checkmk teams** on the object or its site): `nb_<team>` (see) and `nb_<team>_notify` (notify) |
| `parents` | optional per profile: a VM's hypervisor (assigned device, else all devices of the cluster) or the cable peer of a device's primary interface; untouched when off |
| Cluster host | per cluster type or cluster: label only (default), normal host, Checkmk cluster host with the monitored devices as nodes, or not transferred |
| Downtime | status `offline`/`failed` keeps the host and sets a downtime; removed when active again |
| `locked_by` / `locked_attributes` | marks the host as managed by this NetBox and locks the managed attributes in the Checkmk UI |

Besides hosts, and each marked `managed by NetBox (<installation_id>/<prefix>)` (foreign
objects are never changed): the own tag groups; the contact groups per team; the users'
memberships in them (matched by email; only the own groups change); one notification rule
per team that notifies exactly the `_notify` group about its hosts; optionally the rules
*Periodic service discovery* and *Clustered services*; optionally users without login for
contacts without a Checkmk user; the downtimes.

In NetBox (menu **Checkmk Sync**):

| Menu item | What it shows |
|---|---|
| **Instances** | per instance: setup check, channels, host states, recent deployments; buttons **Export**, **Setup wizard**, **Changes**, **Activate in Checkmk**, **Consistency report**, **Run setup check** |
| **Mapping profiles** | how objects become hosts: folders, monitoring site, status actions, standard tags, parents, cluster modes, services as labels |
| **Tag groups** | the own tag groups and their values |
| **Notification channels** | where and about what an instance notifies |
| **Git audit** | the optional Git mirror of the revisions |
| **Hosts** | every object with its state, messages, pending change and history |
| **Deployments** | every preview and deploy: plan, safety gate, approval, result per operation |
| **Revisions** | the state actually applied after each deploy; compare, roll back |
| **Consistency reports** | the comparison NetBox ↔ last revision ↔ Checkmk |

Also: the card **Checkmk configuration** on device, VM and cluster pages, the list column
**Checkmk configuration** (add it with **Configure Table**), a counter in the top bar, a
dashboard widget **Checkmk Sync**, and a REST API under `/api/plugins/checkmk-sync/`
(instances, profiles, tag groups, channels read/write; hosts, deployments, revisions and
reports read-only; secrets write-only; no Git remotes and no actions such as deploy).

### Custom fields

Created by the plugin after `migrate` (group *Checkmk*):

| Field | Label | On | Purpose |
|---|---|---|---|
| `checkmk_instance` | Checkmk instance | device, VM, cluster | the instance that monitors the object; empty = not monitored |
| `checkmk_profile` | Checkmk profile | device, VM, cluster | another profile than the instance's default |
| `checkmk_hostname` | Checkmk host name | device, VM, cluster | another host name than the NetBox name |
| `checkmk_site` | Checkmk monitoring site | device, VM, cluster | the monitoring site in a distributed setup |
| `checkmk_labels` | Checkmk labels | device, VM, cluster | free labels (JSON), get the prefix |
| `checkmk_teams` | Checkmk teams | device, VM, cluster, site | teams (contact groups) in addition to the contacts' teams |
| `checkmk_tag_<slug>` | Checkmk *title* | device, VM, cluster | value of an own tag group, e.g. `checkmk_tag_criticality` |
| `checkmk_address` | Checkmk address | cluster | IP or DNS name of a cluster host |
| `checkmk_clustered_services` | Checkmk clustered services | cluster | services that run on the cluster host |
| `checkmk_cluster_mode` | Checkmk cluster mapping | cluster | label only, normal host, Checkmk cluster host, do not transfer |
| `checkmk_parents` | Checkmk parents of the VMs | cluster | off, assigned device, all devices |

## Guides

### Daily work: change, preview, deploy

NetBox stays the source of truth: you change NetBox, the plugin shows what that means for
Checkmk, and nothing reaches Checkmk until someone deploys.

1. **Change NetBox as usual:** rename a device, change its primary IP, role, site, tags,
   description or status, assign or unassign the instance. Every save recomputes the
   pending changes of the affected instance (no Checkmk call).
2. **See what is pending:** the top-bar counter, the device's card **Checkmk
   configuration** (state *Change pending*), or instance > **Changes**. Each change shows
   the changed fields, the author and the time of the newest NetBox change, also indirect
   ones ("indirect: site FRA1 changed by anna"). A click on the counter leads to the next step, like *Activate pending changes*
   in Checkmk: the plan waiting for approval, or **Changes** with the preview buttons.
3. **Preview:** on **Changes** choose
   - **Preview all changes**, everything pending (including tag groups, contact groups
     and rules);
   - **Preview my changes**, only objects only you changed;
   - **Preview selection**, the ticked objects.
4. **Check the plan** on the deployment page: the operations, the card **Safety gate**
   (verdict and reasons), **Not deployable** (conflicts, these objects are left out) and
   **Notes**.
5. **Deploy** with **Deploy**, or wait for [approval](#approval-and-four-eyes-principle)
   if the gate requires it. **Discard** drops a preview you do not want; **Discard my
   open previews** on **Changes** drops all of yours. Previews nobody deployed close by
   themselves after *Previews expire after (hours)* (instance, default 24, 0 = never);
   plans waiting for approval stay.
6. **Activate** in Checkmk (**Activate in Checkmk** or in Checkmk), unless *Activate
   after deploy* is on for the instance.

Good to know:

- The deploy recomputes the plan. If NetBox or Checkmk changed since the preview, the
  deployment becomes *Stale* and nothing is written: preview again.
- An error stops only the affected operation; dependent ones are skipped and named. The
  deployment is then *Applied with errors* and the host shows the reason.
- A rename runs only when Checkmk has no pending changes; otherwise it waits (*Waiting for
  activation*) and runs with the next deploy after activation.

How a deployment moves through its states:

```mermaid
stateDiagram-v2
    state "Planned" as Planned
    state "Needs confirmation (first deploy)" as Confirm
    state "Awaiting approval" as Approval
    state "Queued" as Queued
    state "Running" as Running
    state "Applied" as Applied
    state "Applied with errors" as Partial
    state "Failed" as Failed
    state "Stale" as Stale
    state "Aborted" as Aborted
    [*] --> Planned: preview within the limits
    [*] --> Confirm: first deploy of the instance
    [*] --> Approval: limits exceeded, tag value in use
    [*] --> Aborted: desired set shrank sharply, prerequisite missing
    Confirm --> Queued: confirm and Deploy
    Approval --> Planned: Approve this plan
    Planned --> Queued: Deploy
    Queued --> Running: background worker
    Running --> Stale: NetBox or Checkmk changed since the preview
    Running --> Applied: all operations ok
    Running --> Partial: some operations failed
    Running --> Failed: nothing applied
    Planned --> Aborted: Discard
    Confirm --> Aborted: Discard
    Approval --> Aborted: Discard
    Stale --> [*]: preview again
```

### Approval and four-eyes principle

A plan needs approval when it exceeds the limits, removes a tag value or tag group that
hosts still use, or when an aborted shrink is overridden.

1. The preview ends in state *Awaiting approval*; the card **Safety gate** says why.
   Channels with the trigger *Approval needed* notify the approvers.
2. A user with `approve` opens the deployment and clicks **Approve this plan**. Exactly
   the stored plan is approved.

   ![A plan awaiting approval, seen by an approver](https://gitlab.thatsallmine.de/checkmk1/netbox_checkmk_sync/-/raw/main/docs/screenshots/awaiting-approval.png)

3. Then someone with `deploy` (and `deploy_foreign` for other people's changes) clicks
   **Deploy**.

**Four-eyes principle** (instance option, recommended for production): nobody approves a
plan they created or that contains their own changes. If the plan contains changes of
unknown authors (e.g. through a config context or a cable), nobody who changed anything in
NetBox since the last revision may approve it.

Default limits, scaled with the instance size (deletions count if they exceed the absolute
*or* the relative limit, changes only if they exceed both):

| Hosts in the instance | Deletions | Changes | Creations |
|---|---|---|---|
| up to 200 | 5 or 10 % | 25 and 20 % | 50 |
| up to 2000 | 10 or 5 % | 100 and 10 % | 100 |
| more | 25 or 2 % | 250 and 5 % | 250 |

The **first deploy** of an instance is always confirmed explicitly. A **desired set that
shrinks by more than 50 %** is aborted (e.g. a filter or an assignment removed by
mistake); only a superuser can override it (checkbox **Override abort** on the Changes
page), and the plan then still needs approval. Deploys without approval within 24 hours
count together against the limits. All numbers can be overridden per instance (*Limit
overrides*, e.g. `{"tiers": [{"delete_abs": 3}], "shrink_abort_pct": 30}`).

### Responsibility for a group of hosts

By default, the plugin's permissions apply to a whole instance. With the instance option
**Responsibility from object permissions**, deploying, approving and rolling back need,
for every object in the plan, the NetBox permission to change it. The responsibility then
comes from the device, VM and cluster permissions you maintain anyway.

Example: a lead for the site FRA1.

1. **Admin > Authentication > Groups:** create the group *Lead FRA1* and add the users.
2. **Admin > Authentication > Permissions > Add:**
   - name *Lead FRA1: change FRA1*, object types *DCIM > device* and *Virtualization >
     virtual machine*, actions *view* and *change*, constraints `{"site__slug": "fra1"}`,
     group *Lead FRA1*;
   - name *Lead FRA1: Checkmk*, object type *Netbox_Checkmk_Sync > Checkmk instance*,
     action *view*, **Additional actions** `deploy`, `deploy_foreign`, `approve`, group
     *Lead FRA1*;
   - name *Lead FRA1: Checkmk records*, object types host binding, deployment, revision,
     consistency report, mapping profile, action *view*, group *Lead FRA1*;
   - name *Lead FRA1: view all*, all object types, action *view*, group *Lead FRA1*
     (without it the device form offers no device types, tenants or tags, and saving
     fails), unless the group has read access already.
3. On the instance, **Edit** and tick **Responsibility from object permissions**.

The members now deploy and approve every change in FRA1, also other people's, but no plan
with an object elsewhere: the deployment page names those objects and hides the buttons.
Objects already deleted from NetBox cannot be checked; they need the additional action
`deploy_unrestricted` or a superuser. Automatic mode is not limited.

### Teams, contacts and notification rules

Who sees and who is notified about a host comes from NetBox contacts. No group per person,
no LDAP assumed; users are matched by email.

1. **Switch the function on:** instance > **Setup wizard**, step *Functions*, tick
   **Contacts**, **Save functions and check**. The plugin creates the contact group
   *Checkmk* with *Checkmk &lt;instance&gt; - Users* and *Checkmk &lt;instance&gt; -
   Notification*.
2. **Enable contacts:** put a contact into *- Users* (sees the hosts of their teams) or
   *- Notification* (also notified).
3. **Teams:** put the contact into a NetBox contact group, e.g. *Team Linux*
   (slug `linux`). That becomes the Checkmk contact groups `nb_linux` and
   `nb_linux_notify`.
4. **Assign the contact** to devices, VMs or clusters (contact assignment, any role), or
   set the custom field **Checkmk teams** on the object or its site.
5. Preview and deploy. Checkmk gets the contact groups, the hosts get them, the users with
   the contact's email address get the memberships, and one notification rule per team
   notifies the `_notify` group (method HTML email).

```mermaid
flowchart LR
    C["NetBox contact<br/>with email"] -- member of --> T["NetBox contact group<br/>e.g. Team Linux (linux)"]
    C -- enabled in --> E["Checkmk &lt;instance&gt; - Users<br/>or - Notification"]
    C -- assigned to --> O["device, VM, cluster<br/>(or field Checkmk teams)"]
    T --> G["Checkmk contact groups<br/>nb_linux, nb_linux_notify"]
    O --> H["host gets<br/>nb_linux, nb_linux_notify"]
    E --> U["Checkmk user with the same email<br/>gets the memberships"]
    G --> R["notification rule per team<br/>notifies nb_linux_notify"]
```

Options: **Create missing users** creates users without login (no password, *Disable
password* ticked; notifications still work) for contacts without a Checkmk user; **Visibility via contact groups only** checks that enabled users lack
*See all host and services*. The consistency report lists contacts without a user, without
email, with duplicates or under an LDAP connection.

### Own tag groups and standard tags

1. **Checkmk Sync > Tag groups:** the groups *criticality* (prod/test/dev) and
   *monitoring* (full/basic/ping) exist; add your own with **Add** (the slug becomes
   `nb_<slug>` and can never be renamed).
2. **Set values per object:** the custom field `checkmk_tag_<slug>` (e.g.
   **Checkmk criticality**), or a NetBox tag via *Tag mapping*, or the config context
   `{"checkmk": {"tags": {"criticality": "test"}}}`; otherwise the group's default.
3. **Derived groups** take the value from the role, platform, site or tenant (*Derived
   from*).
4. **Standard tags** (`agent`, `snmp_ds`, `piggyback`) are set only if the profile says so,
   e.g. `[{"match": {"platform": ["ios"]}, "tags": {"agent": "no-agent", "snmp_ds": "snmp-v2"}}]`.

Removing a tag value or a whole group that hosts still use needs approval. The panel on
the device shows each value with its origin (custom field, tag, config context, default).

### Labels, folders and the profile

- **Folders** come from the profile's *Folder template* with the placeholders `{prefix}`,
  `{type}`, `{site}`, `{role}`, `{platform}`, `{tenant}`, `{cluster}`. Changing it moves
  the hosts with the next deploy.
- **Free labels:** custom field **Checkmk labels** (`{"app": "shop"}`) or config context
  `{"checkmk": {"labels": {"app": "shop"}}}`; they always get the prefix (`nb/app:shop`).
- **Services as labels** (profile): NetBox services of the object become
  `nb/service/<name>:yes`.
- **Another profile** for single objects: custom field **Checkmk profile**.
- **Host name:** custom field **Checkmk host name**; with *Enforce lower case* names are
  lowercased; with *Invalid host names* = *Convert automatically* invalid characters are
  replaced instead of reported.
- **Without primary IP:** the profile option *Without primary IP* = *Create without IP
  (resolve via DNS)* creates the host anyway; default is an error.
- **Filter per instance** (roles, platforms, tags): objects outside it are treated like
  unassigned ones.

### Clusters and parents

**Clusters** (custom field **Checkmk cluster mapping** on the cluster, or *Cluster modes*
per cluster type in the profile, e.g. `{"proxmox": "cluster"}`):

| Mapping | Result |
|---|---|
| Label only (default) | no host; the members get `nb/cluster` and `nb/cluster_type` |
| Normal host | one host for the cluster endpoint; needs **Checkmk address** or config context `checkmk.address` |
| Checkmk cluster host | a Checkmk cluster with the monitored devices as nodes (VMs only if the cluster has no devices); **Checkmk clustered services** fills the rule *Clustered services* |
| Do not transfer | nothing |

The cluster itself must be assigned to the instance (field **Checkmk instance**), and the
nodes must be monitored by the same instance and site.

**Parents** (profile option *Parents*, or custom field **Checkmk parents of the VMs** on a
cluster): *VMs: assigned device, else all devices of the cluster*, *VMs: always all devices
of the cluster*, or *Devices: cable peers of the primary IP's interface*. *Off* leaves
parents set in Checkmk untouched.

### Distributed monitoring

The instance is the central site: the configuration goes there and activating changes
replicates it to the remote sites. The monitoring site of each host comes from, in this
order:

1. the custom field **Checkmk monitoring site** on the object;
2. the config context `checkmk.site`, e.g. one config context per NetBox site or region
   with `{"checkmk": {"site": "remote1"}}`;
3. the profile's *Monitoring site*;
4. the central site.

```mermaid
flowchart LR
    A{"Custom field<br/>Checkmk monitoring site"} -- set --> V
    A -- empty --> B{"Config context<br/>checkmk.site"}
    B -- set --> V
    B -- empty --> C{"Profile<br/>Monitoring site"}
    C -- set --> V
    C -- empty --> Z["central site"] --> V
    V{"known to the instance?<br/>(last setup check)"} -- yes --> OK["host on that site"]
    V -- no --> ERR["error on the object,<br/>no host"]
```

Run the setup check after adding a site in Checkmk: it reads the site list. A site the
instance does not know is an error on the object (no host); parents on another site are
left out with a note (Checkmk does not support them); cluster nodes must be on the cluster
host's site.

### NetBox status, downtimes and the grace period

| NetBox status | Default action |
|---|---|
| active | host is created and kept in sync |
| planned, staged, inventory | no host is created; an existing host stays unchanged |
| offline, failed | host stays in sync and gets a downtime (365 days), removed when active again |
| decommissioning | treated like a deleted object |

Change it in the profile (*Status actions*, values `create`, `keep`, `downtime`, `delete`).
A new downtime is set with the next deploy after the host was activated in Checkmk.

**Soft deletion:** when an object is deleted, unassigned, filtered out or decommissioned,
its host is first marked with the label `nb/orphaned_since:<date>` and deleted only after
the grace period (instance option, default 7 days, 0 = right away) with a later deploy.
**Delete now** on the host page skips the grace period; limits and approval still apply.
An object that comes back before loses the mark.

**Hand back instead of deleting:** with the instance option *When an object leaves* =
*Hand the host back to manual configuration after the grace period*, the host of such an
object goes through the same grace period, and when it is over it stays in Checkmk and the
integration lets go of it with the next deploy: it removes `locked_by`, the `nb/...`
labels, the values of the own tag groups, the own contact groups and the own downtimes.
Folder, address, alias, site, parents and standard tags stay and are from then on
configured by hand. **Hand back** on the host page hands a single host back with the next
deploy, without waiting and also while the instance deletes the others; **Delete now** wins
over both. Hand-backs count like deletions for the limits. The hand-back runs in three
steps (labels, then the lock and the tags, last the `nb/id` label): if one fails, the host
is still the integration's and the next deploy hands it back. If the object comes back
later, its host is a foreign one: its host page shows *Handed back: adopt to manage again*
and offers [Adopt](#taking-over-existing-hosts-adopt) right away, the preview reports the
conflict.

The state of a host (list **Checkmk Sync > Hosts**, card on the device):

```mermaid
stateDiagram-v2
    state "Scheduled for creation" as Scheduled
    state "Monitored" as Monitored
    state "Change pending" as Pending
    state "Waiting for activation" as Waiting
    state "Scheduled for deletion" as Deleting
    state "Scheduled for hand-back" as Releasing
    state "Orphaned (grace period)" as Orphaned
    state "Handed back: adopt to manage again" as HandedBack
    state "Rollback lock" as Locked
    state "Error" as Error
    [*] --> Scheduled: field Checkmk instance set
    [*] --> Error: invalid name, no IP, unknown site
    Error --> Scheduled: fixed in NetBox
    Scheduled --> Monitored: deploy
    Monitored --> Pending: object changed in NetBox
    Pending --> Monitored: deploy
    Pending --> Waiting: rename waits for activation
    Waiting --> Monitored: activate, deploy
    Monitored --> Deleting: object deleted, unassigned, filtered out
    Deleting --> Orphaned: deploy marks the host
    Orphaned --> Pending: object back in NetBox
    Orphaned --> Deleting: grace period over, or Delete now
    Deleting --> [*]: deploy deletes the host
    Monitored --> Releasing: object leaves, hand-back on
    Releasing --> Orphaned: deploy marks the host
    Orphaned --> Releasing: grace period over with hand-back on, or Hand back
    Releasing --> [*]: deploy hands the host back
    [*] --> HandedBack: object of a handed-back host assigned again
    HandedBack --> Scheduled: Adopt host
    Monitored --> Locked: rollback
    Locked --> Monitored: NetBox matches again, or Release lock
```

### Taking over existing hosts (Adopt)

A host that already exists in Checkmk with the wanted name, but was not created by this
NetBox, is never overwritten.

1. Assign the object and preview: the deployment shows it under **Not deployable**: "a
   host named 'x' already exists in Checkmk and is not managed by this NetBox; rename one
   of them or adopt the host".
2. Open the host (**Checkmk Sync > Hosts**, or the link on the device) and click **Adopt
   host** (permission `adopt`).

   ![Host page with the conflict and the button Adopt host](https://gitlab.thatsallmine.de/checkmk1/netbox_checkmk_sync/-/raw/main/docs/screenshots/adopt.png)

3. Preview again: it shows exactly what changes on the host (folder, attributes, labels).
   Deploy.

From then on the host belongs to the integration, also when it is removed.

### Rollback

Every deploy stores a revision with the state actually applied.

- **Undo this revision** (revision page): back to the state before this revision.
- **Roll back instance to rN**: everything back to revision N.
- **Roll back selected hosts to rN** (revision page) or **Roll back to rN** (host history):
  only these hosts.

At the bottom of a revision page, *Compare with another revision* shows the differences to
any other revision and offers **Roll back instance to** that revision right there. Every
rollback button asks for confirmation first, and so does the final **Roll back**.

![Revision page with the rollback buttons](https://gitlab.thatsallmine.de/checkmk1/netbox_checkmk_sync/-/raw/main/docs/screenshots/revision.png)

A rollback is a plan like a deploy (preview, safety gate, approval, permission
`rollback`) and becomes a new revision. Missing folders, contact groups and tag values are
restored; hosts created later are deleted (without grace period, the preview lists them).
Only the configuration comes back.

Afterwards each rolled-back object has a **rollback lock**: its NetBox changes are not
deployed until NetBox matches the rolled-back state again (the lock then releases itself).
To deploy the NetBox state anyway, open the host and use **Release lock** with a comment.

![Host with a rollback lock and Release lock](https://gitlab.thatsallmine.de/checkmk1/netbox_checkmk_sync/-/raw/main/docs/screenshots/rollback-lock.png)

### Consistency report

Compares NetBox, the last revision and Checkmk and changes nothing: nightly at the
instance's *Report at (hour)* (default 2, local time) or on demand with **Consistency
report** on the instance page.

![Consistency report with findings, filter and export](https://gitlab.thatsallmine.de/checkmk1/netbox_checkmk_sync/-/raw/main/docs/screenshots/report.png)

| Finding | Severity | Meaning |
|---|---|---|
| Prerequisite missing | error | a mandatory prerequisite is missing (deploys are blocked) |
| Pending | info | NetBox differs from the last revision; not deployed yet |
| Drift | warning | the host was changed in Checkmk since the last revision (fields listed) |
| Missing in Checkmk | error | a host of the last revision is gone or lost its marker |
| Orphaned | warning | NetBox wants no host for this object any more; marked since a date |
| Conflict | warning | a foreign host blocks the wanted name |
| Namespace | error | foreign objects use the prefix, or own objects were changed in Checkmk |
| Not activated | warning | changes pending in Checkmk longer than *Not activated after (hours)* |
| Users | info | contact reconciliation (no user, no email, duplicates, LDAP) |
| Coverage | info | optional: active objects with an IP that no instance monitors |
| Plausibility | info | NetBox platform vs `cmk/os_family` in Checkmk |

New findings since the last report are highlighted; filter by severity, kind or *only
new*; export as **CSV** or **JSON**.

### Notification channels

1. **Checkmk Sync > Notification channels > Add**: instance, name, kind.
2. Per kind: **NetBox** (bell for the members of a notification group), **Email** (NetBox's
   mail settings, recipients one per line or comma-separated), **Pushover** (user or group
   key and app token, priority per severity), **Webhook** (generic JSON, Slack, Microsoft
   Teams, ntfy).
3. **Triggers:** new findings in the consistency report (from *Report findings from*),
   approval needed, deploy failed or partly failed, blocker, rollback lock open for too
   long, hosts about to reach the end of their grace period.
4. Save, then **Send test message** on the channel page.

The same message is sent once per 24 hours; **Last messages** on the channel page shows
what was sent and errors.

### Schedule and automatic mode

The system job *Checkmk sync schedule* runs every 15 minutes in the background worker. Per
instance it recomputes the pending changes every *Recompute every (minutes)* (default 60;
the run that comes closest, so a value that is not a multiple of 15 is rounded to the
tick), runs the report at its hour, and warns about hosts close to the end of their grace period
and old rollback locks. Only one run at a time; a run that has to wait decides afterwards
what is still due.

*Operating mode* **Manual** (default): the schedule only recomputes, a human deploys.
**Automatic within limits:** the schedule deploys everything that passes the safety gate;
plans that need approval wait, and the first deploy is always confirmed by hand.

### Git audit and ZIP export

Optional: every revision becomes a commit in a Git repository of your choice (one folder
per instance with a file per host, the tag and contact groups and the revisions).

1. Create an empty repository and an access: an HTTPS token with write access, or an SSH
   deploy key.
2. **Checkmk Sync > Git audit > Add:** repository URL (`https://...`, `ssh://...` or
   `git@host:path`), branch, user name and token, or SSH key; for SSH also **SSH known
   hosts** (`ssh-keyscan <host>`), otherwise the host key is trusted on first use.
3. **Test connection** on the page.
4. Optional: *Commit link pattern* (`https://git.example.com/audit/-/commit/{sha}`) and
   **strict** (nothing is deployed while the repository is unreachable).

Each commit names who changed NetBox, who approved and who deployed, as trailers
(`Changes-by:`, `Approved-by:`, `Deployed-by:`). The commit author is the deploying user as
`Full Name <username@netbox>`: Git history cannot be cleaned later, so the users' real email
addresses are only used with **Include email addresses** on the Git remote.

Without Git: **Export** on the instance page downloads all revisions and host states as a
ZIP (needs the permission to view revisions).

### Reservations

Optional per instance (*Reservations*: off, warn, block): before a longer change, take an
object "into editing" with **Reserve** on its card **Checkmk configuration**. With *warn*
others see a notice; with *block* NetBox refuses to save the object for them. Reservations
expire after *Reservations expire after (hours)* (default 2); **Release reservation** ends
one early; `break_lock` releases other people's.

## How it works

```mermaid
flowchart TB
    OBJ["NetBox: devices, VMs, clusters<br/>with the field 'Checkmk instance'"]
    DES["Desired state<br/>profile, labels, tags, teams"]
    PEND["Pending changes<br/>with author"]
    PREV["Preview<br/>plan from the current Checkmk state"]
    GATE{"Safety gate<br/>limits, first deploy"}
    APP["Approval<br/>optionally four-eyes"]
    DEP["Deploy<br/>background job"]
    REV["Revision<br/>what was actually applied"]
    GIT[("Git repository<br/>audit, optional")]
    subgraph CMK["Checkmk, REST API only"]
        HOSTS["Own hosts and folders<br/>locked_by, nb/ labels"]
        OWN["Own tag groups, contact groups,<br/>notification rules, downtimes"]
        ACT["Activate changes<br/>off by default"]
    end
    REP["Consistency report<br/>nightly or on demand"]
    NOTI["Notifications<br/>NetBox, email, Pushover, webhooks"]

    OBJ --> DES --> PEND --> PREV --> GATE
    GATE -- within the limits --> DEP
    GATE -- limits exceeded --> APP --> DEP
    DEP --> CMK
    DEP --> REV
    REV -. compared with .-> PEND
    REV -- rollback --> PREV
    REV -. optional .-> GIT
    CMK -. compared by .-> REP
    REP --> NOTI
```

- **Desired state** comes from NetBox; **pending changes** compare it with the last
  revision (no Checkmk call); the **preview** reads Checkmk and computes the exact plan.
- **Ownership:** only hosts whose `locked_by` carries `program_id = netbox` and this
  installation's and instance's `instance_id` (`<installation_id>-<prefix>`, earlier
  `<installation_id>/<prefix>`, which the next deploy replaces) are changed or
  deleted; tag groups, contact groups, rules and downtimes carry the marker
  `managed by NetBox (<installation_id>/<prefix>)`. A second instance or NetBox on the same
  Checkmk never touches them. The label `nb/id` is the identity, so a renamed NetBox object
  becomes a rename in Checkmk. Labels set manually in Checkmk are kept.
- **Order** (partial success): renames, contact groups, tag groups, notification rules,
  memberships, folders, hosts (parents before their children, cluster hosts after their
  nodes), moves, updates, nodes, orphan markings, deletes, then the cleanup in reverse and
  finally rules and downtimes. A bulk call rejected because of one entry is resent without
  it.
- **Optimistic locking:** the deploy recomputes the plan and compares it with the preview
  (*Stale* if anything changed). Optionally hosts are updated one by one with If-Match
  (*Single updates with ETag*) to close the window completely.
- **Prerequisites:** the core needs no rules; enabled functions add prerequisites
  (permissions, edition, templates for the Checkmk version, visibility via contact groups
  only). A missing mandatory prerequisite blocks the deploys. Rule templates exist for
  Checkmk 2.5 and 3.0; for other versions nothing is created automatically and the wizard
  shows instructions.
- **Changing the instance** of an object: the old host is only deleted once the host exists
  in the new instance.
- **Secrets** (Checkmk, Pushover, Git) are Fernet-encrypted with a key derived from
  `SECRET_KEY`; if `SECRET_KEY` changes, enter them again. They are never shown again, are
  write-only in the REST API and appear in NetBox's change log only as `(set)`; webhook
  URLs, which often contain a token, are treated the same way. A stored secret is only sent
  where it was entered for: changing the Checkmk URL, TLS verification or CA bundle, or the
  URL of a Git repository, requires entering it again.
- **Never:** shell or file access to Checkmk, changes to foreign hosts, foreign rules,
  foreign tag or contact groups, or anything on users except their memberships in the own
  contact groups. Activation in Checkmk is off by default.

### Configuration

`PLUGINS_CONFIG["netbox_checkmk_sync"]`:

| Key | Default | Meaning |
|---|---|---|
| `installation_id` | `netbox` | part of `locked_by.instance_id`; unique per NetBox installation writing to the same Checkmk |
| `netbox_url` | empty | base URL of this NetBox for links in notifications |
| `pushover_url` | Pushover API | only for tests or a proxy |
| `extra_adapters` | empty | further NetBox models as sources, e.g. objects of a cloud plugin: `{"app.model": {"type": "cloud", "ip4": "public_ip", "site": "region"}}` |

The other settings are per instance (**Instances > Edit**): connection, mapping (prefix,
default profile, host names), filter, functions, deploy (activation, four-eyes,
responsibility, grace period, batch size, single updates, limit overrides, revisions to
keep, reservations, preview expiry), schedule and report.

### Permissions

NetBox object permissions on *Checkmk instance* with these actions. The plugin's own
actions are not listed in NetBox: type them into **Additional actions** of a permission.

| Action | Allows |
|---|---|
| `view` | see the instance, its changes, deployments and reports; create previews; run a consistency report; open **Activate in Checkmk** |
| `change` | edit the instance, run the setup check and the wizard |
| `change_tls` | change the TLS trust: switch *Verify TLS* off, set a *CA bundle* (with `change`) |
| `deploy` | deploy plans that contain only one's own changes; discard previews |
| `deploy_foreign` | deploy plans that contain other people's changes (this includes changes of unknown authors and all tag group, contact group and rule changes) |
| `approve` | approve plans that need approval; with four-eyes not one's own |
| `delete_now` | skip the grace period of an orphaned host |
| `hand_back` | hand the host of an orphaned object back to manual configuration instead of deleting it |
| `rollback` | run and discard rollbacks, release rollback locks |
| `adopt` | take over a foreign host with a wanted name |
| `break_lock` | release other people's reservations |
| `activate` / `activate_foreign` | activate own / also foreign changes in Checkmk; exactly the changes the page showed |
| `deploy_unrestricted` | with *Responsibility from object permissions*: plans with any objects |

Also needed: `view` on host binding, deployment, revision and consistency report to see
those pages; `change` on a notification channel or Git remote for **Send test message**
and **Test connection**; NetBox `change` on the device, VM or cluster to reserve it; the
global permission to view revisions for **Export**.

#### Recommended roles (least privilege)

Nothing is limited to NetBox administrators: every function is a NetBox object permission
(**Admin > Authentication > Permissions**), assigned to groups and optionally restricted
with a constraint, e.g. to one instance: `{"name": "checkmk-prod"}`. Superusers have every
permission; keep them few, only they can override the abort on a sharply shrinking desired
set.

| Role (NetBox group) | Object types | Actions |
|---|---|---|
| Viewer (e.g. NOC) | Checkmk instance, host binding, deployment, revision, consistency report | `view` |
| Editor (maintains devices and VMs) | as viewer | `view`; on Checkmk instance also `deploy` (own changes) |
| Lead | Checkmk instance | `view`, `deploy`, `deploy_foreign`, `activate`, `delete_now` |
| Approver | Checkmk instance | `view`, `approve` (other people than the deployers when four-eyes is on) |
| Integration admin | Checkmk instance, mapping profile, tag group, notification channel, Git remote | `view`, `add`, `change`, `delete`; on Checkmk instance also `change_tls`, `rollback`, `adopt`, `break_lock`, `activate_foreign` |

- **Who assigns objects:** whoever may change a device, VM or cluster can set the field
  **Checkmk instance**. That only creates pending changes; nothing reaches Checkmk without
  `deploy`. NetBox has no permission per field; if needed, restrict the device permission
  itself with constraints (site, tenant).
- **Read access for editing:** NetBox's edit forms offer related objects (device type,
  manufacturer, tenant, tags, IP addresses, ...) only to users who may view them; without
  that a device cannot be saved. Give editors and leads `view` on all object types (one
  permission without constraint) and constrain only `change`. **Edit** on an object outside
  one's `change` constraint answers "The requested page does not exist" - that is NetBox's
  behavior, not an error of the plugin.
- **Automatic mode** deploys within the limits without a person, so everyone who may change
  devices then effectively deploys. Keep production instances in manual mode, or rely on
  the limits and approval, and switch on **four-eyes** for production.
- **`change` on the instance is sensitive:** it can switch off four-eyes and change the limits
  (the TLS trust additionally needs `change_tls`). Give it to integration admins only. Notification channels and Git
  remotes send data to other systems; restrict who may add them.
- **A group of hosts per person:** see
  [Responsibility for a group of hosts](#responsibility-for-a-group-of-hosts).
- **Separate instances** for production and test, with permissions constrained per
  instance.
- **REST API:** the same permissions apply; use API tokens of users that hold only what
  the task needs.
- **Checkmk side:** one dedicated automation user per instance with its own role holding
  only the permissions listed under [Requirements](#requirements), not the admin role.

## Alongside an existing configuration

The integration runs in parallel to what is already configured on the Checkmk site. It is
not an all-or-nothing switch: it can be added to a running site and removed again without
touching the rest of the configuration.

- **Own namespace:** hosts go into their own folder tree (default `/nb/{site}/{role}`), own
  tag groups and contact groups start with `nb_`, labels with `nb/`. Only objects with the
  integration's mark are ever changed or deleted. Hosts, folders, rules, tag groups,
  contact groups and users configured by hand stay untouched; labels set by hand on managed
  hosts are kept.
- **No global changes:** no global settings, no shell or file access to Checkmk.
  Activating changes is off by default, so the site's existing activation routine stays in
  charge.
- **Step by step in:** assign the instance to a few devices or VMs first (or narrow it with
  the filter per instance: roles, platforms, tags), check the preview, deploy, then extend.
  An existing host with a wanted name is reported as a conflict and never overwritten;
  [Adopt](#taking-over-existing-hosts-adopt) takes it over deliberately.
- **Out again:** the integration removes what it created and leaves the rest alone, or
  hands its hosts back to manual configuration; see [Removing it](#removing-it).

## Removing it

1. **Remove the assignment.** Clear the field **Checkmk instance** on all devices, VMs and
   clusters (bulk edit) and deploy, with the functions still switched on. Removing more
   than half of the hosts at once is aborted; a superuser deploys it with **Override
   abort**, and the plan needs approval. The hosts are marked as orphaned and deleted once
   the grace period is over (another deploy, or **Delete now**; with a grace period of 0
   right away), together with the own contact groups, the memberships in them and the
   notification rules. Hosts taken over with **Adopt** belong to the integration and are
   deleted as well. **To keep the hosts**, set *When an object leaves* to *Hand the host
   back to manual configuration* first: the deploy then hands every host back instead (see
   [the grace period](#netbox-status-downtimes-and-the-grace-period)); they stay in their
   folders and are configured by hand from then on.
2. **Switch the functions off** in the setup wizard (own tag groups, contacts, rules) and
   deploy again: the own tag groups and rules are removed. Nothing marked
   `managed by NetBox (...)` is left in Checkmk. Activate the changes in Checkmk.
3. **Delete the instance** under **Checkmk Sync > Instances**. This deletes its data in
   NetBox (bindings, deployments, revisions, reports, channels); Checkmk is not called.
   To keep the history, use **Export** or the Git audit first.
4. **Drop the plugin's tables** while the plugin is still installed:

   ```console
   cd /opt/netbox/netbox
   python manage.py migrate netbox_checkmk_sync zero
   ```

   This also deletes the custom fields **Checkmk instance** and **Checkmk profile**: they
   point to the plugin's objects, and without the plugin NetBox's device, VM and cluster
   lists fail on them. If you skip this step, delete these two fields by hand before
   removing the plugin.
5. **Remove the plugin:** take `"netbox_checkmk_sync"` out of `PLUGINS` and its block out
   of `PLUGINS_CONFIG`, restart NetBox and its worker
   (`systemctl restart netbox netbox-rq`) and, if wanted, `pip uninstall netbox-checkmk-sync`
   (and remove it from `local_requirements.txt`).
6. **Leftovers** that NetBox keeps and that can be deleted by hand:
   - the job *Checkmk sync schedule* under **Operations > Jobs**;
   - the other custom fields of the group *Checkmk* under **Customization > Custom
     Fields** (keep `checkmk_hostname` if netbox-checkmk uses it) and their choice sets
     (*Checkmk: ...*);
   - the contact group *Checkmk* with its groups *Checkmk &lt;instance&gt; - Users* and
     *- Notification* (only if contacts were used);
   - in Checkmk: the folders the integration created (now empty) and users without login
     it created for contacts.

NetBox works without the plugin also while these leftovers still exist; the integration
test checks this.

## Troubleshooting

First places to look: the host's messages (**Checkmk Sync > Hosts** or the card on the
device), the deployment page (cards **Safety gate**, **Not deployable**, **Notes**,
**Operations**), the instance's **Prerequisites** page, the NetBox log and the worker's
log (`journalctl -u netbox-rq`).

### Setup and connection

| Symptom | Cause | Fix |
|---|---|---|
| Setup check *Connection*: `HTTP 301` or `HTTP 302` | wrong URL: `http` instead of `https`, or not the site URL; redirects are not followed | use the exact site URL, e.g. `https://checkmk.example.com/mysite` |
| *Connection*: `HTTP 401` | wrong user name or secret, or not an automation user | check the automation user; enter the secret again (the field stays empty when unchanged) |
| *Connection*: certificate error | self-signed or internal CA | set *CA bundle* to the CA file on the NetBox server; switch off *Verify TLS* only for tests |
| *Permissions*: `missing: ...` | the automation user's role lacks permissions | add them in Checkmk: **Setup > Users > Roles & permissions** |
| *Permissions*: `could not be verified` | the role lacks `wato.users`, so it cannot read itself | add `wato.users`, or check the role by hand |
| *Namespace*: `prefix 'nb' is in use` | foreign tag groups, contact groups, folders or labels already use the prefix | choose another prefix before the first deploy (it is fixed afterwards); this is a warning, not a blocker |
| `The connection changed: enter the secret again.` | URL, TLS or CA bundle changed | enter the secret again; a stored secret is only sent where it was entered for |
| `the stored secret cannot be decrypted (was SECRET_KEY changed?)` | NetBox's `SECRET_KEY` changed | enter all secrets again (instance, Pushover, Git) |
| Instance page: *Blocked: prerequisite missing* | a mandatory prerequisite is missing | open **Prerequisites**, follow *Do it yourself*, then **Check again** |
| *Templates for this Checkmk version*: no templates | Checkmk version other than 2.5 or 3.0 with contacts or periodic discovery on | create the notification and discovery rules by hand as the instructions say, or switch the functions off |
| *Supported edition*: edition community is not supported | Checkmk Community Edition | use a commercial edition (Pro or Ultimate) |

### Objects and hosts

| Symptom | Cause | Fix |
|---|---|---|
| Object not on **Changes**, not monitored | field **Checkmk instance** empty, status *planned/staged/inventory*, or outside the instance's filter | set the field; check the status and the filter (the host messages name the reason) |
| `invalid host name 'x' ... suggestion: y` | the name contains characters Checkmk does not allow | rename in NetBox, set **Checkmk host name**, or *Invalid host names* = *Convert automatically* |
| `object has no primary IP address` | no primary IPv4/IPv6 | set a primary IP, or profile *Without primary IP* = *Create without IP* |
| `no monitoring site known for this instance` | the setup check has never run | **Run setup check** on the instance |
| `monitoring site 'x' does not exist in this Checkmk instance` | wrong site in the custom field or config context, or a new site the instance does not know yet | correct it, or run the setup check after adding the site |
| `host name 'x' is used by several objects` | two objects map to the same name | give one a **Checkmk host name** |
| `tag group nb_x: unknown value` | the value is not in the tag group | add the value under **Tag groups** or correct the field |
| `note: parent X is on site Y ...` | Checkmk has no parents across sites | accepted; the parent is left out |
| Cluster gets no host | default mapping is *Label only*, or the cluster is not assigned | set **Checkmk cluster mapping** and assign the cluster to the instance |
| `cluster nodes: ... a Checkmk cluster host needs at least one monitored node` | no member is monitored by this instance and site | assign the member devices |
| State *Waiting for activation* | a rename waits until Checkmk has no pending changes | activate in Checkmk, then deploy again |
| State *Rollback lock* | the object was rolled back | change NetBox back, or **Release lock** with a comment |

### Preview and deploy

| Symptom | Cause | Fix |
|---|---|---|
| `Preview failed, nothing was planned: ...` | Checkmk not reachable or answered an error | see the message; fix the connection, then preview again |
| **Not deployable**: `a host named 'x' already exists in Checkmk and is not managed by this NetBox` | a foreign host has the wanted name | rename one of them, or [Adopt](#taking-over-existing-hosts-adopt) |
| **Not deployable**: `several hosts carry nb/id=...` or `has no nb/id label` | labels changed by hand in Checkmk | restore the label or remove the duplicate in Checkmk |
| Deployment *Stale* | NetBox or Checkmk changed between preview and deploy | preview again |
| Deployment *Awaiting approval* | limits exceeded or tag value in use; see **Safety gate** | someone with `approve` approves, or reduce the plan (e.g. **Preview selection**) |
| `desired set shrank from X to Y hosts ... aborted` | more than half of the hosts would go, e.g. a filter or assignment removed by mistake | check the cause; a superuser can tick **Override abort**, then approval follows |
| No **Deploy** button | missing `deploy`, `deploy_foreign` (other authors) or responsibility | see [Permissions](#permissions); the page names objects outside the responsibility |
| `permission 'deploy_foreign' ... required` | the plan contains other people's or unattributed changes | someone with `deploy_foreign` deploys, or use **Preview my changes** |
| `four-eyes principle: you cannot approve ...` | you created the plan or changed something in it | another approver |
| `outside your responsibility (no change permission in NetBox): ...` | *Responsibility from object permissions* is on | someone responsible for those objects, or `deploy_unrestricted` |
| `another deploy is running on this instance` | a deploy is still running (or its worker died; the lock ends after an hour) | wait; a stuck queued or running deployment can be discarded once the lock is gone |
| Deployment stays *Queued* | the background worker does not run | `systemctl status netbox-rq`; start it |
| *Applied with errors*, operation `failed` | Checkmk refused one entry | the operation's message says why; dependent operations are *skipped* |
| `blocked, prerequisite missing` | see **Prerequisites** | fix it, **Check again** |
| `Git repository unreachable in strict mode` | Git audit in strict mode cannot push | fix the repository access, or switch strict off |

### Checkmk activation

| Symptom | Cause | Fix |
|---|---|---|
| `N foreign change(s) pending; activating them needs the permission activate_foreign` | others changed Checkmk too | activate in Checkmk, or someone with `activate_foreign` |
| `Checkmk has new pending changes since this page was loaded; nothing was activated` | someone changed Checkmk after the page was opened | check the list again and activate |
| `Activation failed: ... HTTP 503 ... ui-job-scheduler` | a site service is still starting | the plugin retries twice; otherwise wait and try again, check `omd status` |
| Downtime missing for an offline device | a downtime needs the host to be activated first | activate, then deploy again |

### Report, schedule and notifications

| Symptom | Cause | Fix |
|---|---|---|
| No nightly report, nothing recomputed | the background worker does not run, or the job *Checkmk sync schedule* is missing | start `netbox-rq`; restart NetBox to recreate the job |
| `another scheduled run is still busy; skipped` | a run took longer than 10 minutes | look for slow Checkmk calls in the worker log |
| Finding *Drift* | someone changed a managed host in Checkmk | deploy (NetBox wins) or change NetBox to match |
| Finding *Not activated* | changes wait in Checkmk longer than the configured hours | activate in Checkmk |
| No notification arrives | trigger not selected, severity below *Report findings from*, or sent within the last 24 hours | check the channel, **Send test message**, **Last messages** |
| `webhook answered HTTP N` / `webhook to <host> failed` | wrong URL or the receiver refused | check the URL (redirects are not followed) |
| Pushover: `user key or app token missing` | fields empty | enter both |

### Removal and upgrades

| Symptom | Cause | Fix |
|---|---|---|
| `Cannot delete X: objects are still assigned to it` | the instance is still assigned | clear the field on all objects and deploy first, see [Removing it](#removing-it) |
| NetBox device or VM list fails after removing the plugin | the custom fields **Checkmk instance** / **Checkmk profile** still exist | delete them under **Customization > Custom Fields** |
| Plugin gone after a NetBox upgrade | `upgrade.sh` recreated the virtual environment | add the plugin to `local_requirements.txt`, install it again |
| `upgrade.sh` of NetBox 4.6.10 fails at *Building documentation* | NetBox 4.6.10 does not pin `pymdown-extensions`; version 12 breaks its docs build | install with `PIP_CONSTRAINT` set to a file containing `pymdown-extensions<12` |

## Known limitations

- Not tried on a production installation yet. The scale test took 1000 devices through
  the whole cycle; larger setups are estimated only, and the preview runs inside the web
  request (see [Size and timing](#size-and-timing)).
- Without known hosts stored on a Git remote, the SSH host key is trusted on first use.
- Webhook and Git URLs may point to any host the NetBox server can reach (self-hosted ntfy
  or Git are common); grant the permission to add channels and Git remotes accordingly.
  Webhooks do not follow redirects and their answers are not shown.
- The notification rule per team uses the HTML email method; other methods (Slack, Teams
  ...) can be added in Checkmk by hand.
- Handed-back hosts keep the values of the standard tags; the folders the integration
  created stay as well.
- The link from Checkmk back to NetBox is set up by hand in Checkmk; the item in the
  prerequisites opens a page with both steps, the values to enter and links into Checkmk.
  The reason: it needs a global setting that the REST API does not offer; the check only
  detects the rule.
- Users of an LDAP connection that syncs contact groups are not given memberships (the
  directory would overwrite them); then no membership is set for the instance at all.
- Applications as a source of labels are covered by NetBox services (`nb/service/<name>`);
  cloud objects by the `extra_adapters` extension point, there is no built-in cloud adapter.
- The interplay with netbox-checkmk on this side: link to its tab and matching of instances
  by URL; what netbox-checkmk shows about this plugin is up to that plugin.
- Filters such as "pending" or "rollback lock" exist in the plugin's host list, not in the
  device list itself.

## Demo

`scripts/demo.sh up` starts one NetBox 4.7.4 with this plugin and one Checkmk 2.5.0p18
site in Docker, with example data and a configured instance, ready for the first deploy.
The example data also shows teams with contacts, own tag groups, standard tags for
switches, services as labels, hypervisors as parents, a Checkmk cluster host, a downtime
for an offline device, a notification channel, and the user `lead-fra1` (no superuser,
responsible for FRA1 only) for [responsibility for a group of hosts](#responsibility-for-a-group-of-hosts).
[netbox-checkmk](https://gitlab.com/checkmk1/netbox_checkmk_plugin) runs next to it with its
own read-only Checkmk user (`netbox-status`, role *Guest user*): after the first deploy and
activation, devices show their monitoring status in the "Checkmk" tab (`COMPANION` picks
its release, `COMPANION=` leaves it out).

NetBox is published on port 8000, Checkmk on port 5000 (`NETBOX_PORT`, `CMK_PORT`), only on
`127.0.0.1` (Checkmk also on the Docker network's gateway address, which NetBox uses).
`DEMO_BIND=0.0.0.0 HOST_ADDRESS=<address>` publishes them on all interfaces, e.g. on a
shared test server. **Demo only:** it uses fixed, publicly known passwords (printed by the
script); never expose it to untrusted networks. `scripts/demo.sh restart` restarts NetBox after code
changes, `down` stops it, `reset` starts over with fresh data.

The [training handbook](https://gitlab.thatsallmine.de/checkmk1/netbox_checkmk_sync/-/blob/main/docs/training.md)
walks through every function on the demo, with exercises, expected results and review
questions.

## Authorship & AI assistance

Christian Wirtz is the author of this project: he specified the requirements, directed the
design and implementation, and reviewed, tested and is responsible for everything in this
repository. Development was carried out with the assistance of Claude (an AI coding
assistant by Anthropic) as a tool. This note is provided in the interest of transparency,
in line with the EU's AI transparency expectations (cf. Regulation (EU) 2024/1689, the AI
Act) — it does not change the authorship, copyright or license below.

## Development

Unit tests for the engine (no NetBox needed) and lint:

```console
pip install pytest requests ruff
pytest tests
ruff check . && ruff format --check .
```

End-to-end test matrix with throwaway Checkmk, NetBox, Postgres and Redis containers:

```console
scripts/integration_test.sh
CMK_IMAGES="checkmk/check-mk-ultimate:2.5.0p18" NETBOX_VERSIONS="v4.7.4" scripts/integration_test.sh
```

| Variable | Default | Meaning |
|---|---|---|
| `CMK_IMAGES` / `NETBOX_VERSIONS` | see the script | the matrix |
| `CMK_CPUS` / `NETBOX_CPUS` | 2 / 1.5 | CPU cap of the containers; empty = no cap |
| `NETBOX_KIND` | `docker` | `classic`: NetBox from its release tarball, plugin via pip (`scripts/classic_netbox.sh`) |
| `DISTRIBUTED` | empty | `1`: adds a remote site and runs `tests/integration/distributed.py` instead of the flow (no removal check) |
| `SCALE` | empty | a number: runs `tests/integration/scale.py` with that many devices instead of the flow and prints the time of each step (`TIME` lines) |
| `COMPANION` / `COMPANION_NETBOX` | `v0.4.3` / `v4.7.4` | release of netbox-checkmk that runs next to this plugin on that NetBox version (interplay checks; it stays when this plugin is removed); the other versions run without it. `COMPANION=` = never. Only with the Docker image, not with `DISTRIBUTED` or `SCALE` |
| `NCS_IT_PREFIX` | `ncs-it` | name prefix of the containers, so that several runs can share one Docker daemon |

The CI pipeline runs one job per combination in stages. A push runs only what its change
needs: plugin code or the flow → Ultimate 2.5 with both NetBox versions;
`scripts/classic_netbox.sh` → classic NetBox. Ultimate 3.0, the
distributed test and the scale test (1000 devices) are manual jobs (e.g. before a merge request or a release). A pipeline
started by hand runs all stages, or one with the variable `NCS_ONLY` set to
`ultimate_2_5`, `ultimate_3_0`, `classic`, `distributed` or `scale`. The jobs need a
Docker daemon on the runner (host socket or `docker:dind`, variable `DOCKER_MODE`, see
`.gitlab-ci.yml`). Every job is explained in
[docs/PIPELINE-JOBS.md](https://gitlab.thatsallmine.de/checkmk1/netbox_checkmk_sync/-/blob/main/docs/PIPELINE-JOBS.md).

The screenshots in this handbook come from `scripts/screenshots.sh`: a throwaway stack
with the demo data, whose containers are called `checkmk.example.com` and
`netbox.example.com`, and Playwright clicking through the guides (`scripts/screenshots.py`).
In CI it is the manual job `screenshots` (or `NCS_ONLY=screenshots`); the pictures are its
artifact.

### Releasing

A release is only produced by pushing a `vX.Y.Z` tag — CI then builds under that exact
version, uploads it to this project's Generic Package Registry, creates a GitLab Release
and publishes to PyPI. The tag must match `__version__` in `netbox_checkmk_sync/__init__.py`.

## Repository layout

```text
netbox_checkmk_sync/
  engine/              # sync logic without Django
    checkmk.py         #   REST client
    mapping.py         #   NetBox facts -> host spec (labels, tags, folders, sites)
    naming.py          #   host names, folders, prefix
    plan.py apply.py   #   plan against the Checkmk state, apply with partial success
    limits.py          #   safety gate
    timing.py          #   expected deploy duration, job time limit
    objects.py         #   tag groups, contact groups, rules
    contacts.py        #   user reconciliation
    report.py          #   consistency report
    rollback.py        #   rollback plan
    templates.py       #   rule templates per Checkmk version
  adapters.py          # NetBox objects -> plain facts (devices, VMs, clusters, extras)
  sync.py              # desired state, pending changes, preview, deploy, rollback, report
  objects_sync.py      # tag groups, contacts and rules on the NetBox side
  prereq.py            # prerequisites and the blocker
  scheduler.py jobs.py # schedule and background jobs
  notify.py dashboard.py reservations.py gitaudit.py companion.py crypto.py
  models.py choices.py # data model
  signals.py           # custom fields, recompute on save, deletion lock
  views.py urls.py forms.py tables.py filtersets.py navigation.py template_content.py
  api/                 # REST API
  templates/           # UI
  migrations/          # 0001 to 0014
tests/
  test_engine.py test_rollback.py test_report.py test_monitoring.py test_timing.py  # unit tests
  integration/flow.py          # end-to-end flow
  integration/removed.py       # NetBox after removing the plugin
  integration/distributed.py   # central and remote site
  integration/scale.py         # many devices, with the time of each step
scripts/
  integration_test.sh  # Checkmk x NetBox test matrix in Docker
  classic_netbox.sh    # NetBox from its release tarball (NETBOX_KIND=classic)
  demo.sh demo_seed.py # demo NetBox + Checkmk with example data
  screenshots.sh screenshots.py  # handbook screenshots (Playwright)
docs/                  # training handbook (EN/DE), pipeline jobs (EN/DE), project avatar, screenshots/ for the handbook
```

See [CHANGELOG.md](https://gitlab.thatsallmine.de/checkmk1/netbox_checkmk_sync/-/blob/main/CHANGELOG.md) for changes.

## License

Copyright 2026 Christian Wirtz. Licensed under the [Apache License 2.0](https://gitlab.thatsallmine.de/checkmk1/netbox_checkmk_sync/-/blob/main/LICENSE). Forks and
redistributions must keep the [NOTICE](https://gitlab.thatsallmine.de/checkmk1/netbox_checkmk_sync/-/blob/main/NOTICE) file (section 4(d) of the license).

## Disclaimer

I did not develop this plugin officially as an employee of Checkmk GmbH, but rather as a private individual.
