Die 7 DHCP-Pools starteten alle bei .100 und ueberlappten damit die beiden Oktett-Baender, die dieses TP gerade belegt: .100-.149 (Server-Hosts) und .150-.199 (App-Services). In VLAN20 ist das eine echte Kollisionsflaeche -- nautobot .154-.158, gitea .157, pve-mcp .173 und shelly-mcp .180 tragen ihre IP GERAETELOKAL und stehen in keinem Lease-Eintrag, der Core kann sie also nicht schuetzen und einem DHCP-Client .154 anbieten. Entscheid 2026-09-21: nur pool-v20 und pool-v30 werden verengt, v20 zuerst. VLAN10/40/50/90/99 bleiben bei .100-.200 -- dort gibt es kein Schema-Band, das etwas gewoenne, und VLAN40/VLAN10 haetten sonst 11 Sonos (.191-.201), den iPhone (.196) und den Mac (.82) mitgezogen. VLAN20 kostet die Verengung nichts: eine statische Reservierung (nas-bf .20.10, in .10x) und 0 dynamische Leases. pool-v30 bleibt bewusst unangetastet -- dort liegen 35 statische Reservierungen IM Fenster .200-.249 (26 Shellys .202-.226/.233, SwitchBot .227, Twinkly .228, Withings .230/.231, lichterketten .236/.237, Nuki .241/.244, CCU .232). Erst Schicht B zieht die um, dann pool-v30. Nebenbei faellt damit die .30.250-Kollision: der Sonoff Dongle-M sitzt dort statisch, der Pool reicht heute bis .250. Neuer Job `tp45-pool-v20` (gc-configplan.py) mit gezaehltem Selektor: ein `find where`, das 0 trifft, sieht wie "nicht vorhanden" aus statt wie "kaputt" -- ein blankes `set [find where name=...]` liefe ins Leere und der Job meldete Erfolg. 🔴 Lehre aus dem ersten Fehlversuch: jedes config_set-Statement muss auf EINER Zeile klammer-vollstaendig sein. Mit `:if (...) do={` am Zeilenende geht RouterOS in den Fortsetzungs-Modus und zeigt einen anderen Prompt, der netmiko-Treiber verliert sein Muster und der Deploy stirbt mit `E1015: Pattern not detected: ...@crs326\-core\].*$|#.*$)` -- Plan auf Failed, ohne etwas geschrieben zu haben. Abnahme: Gerate-Read pool-v20 = 10.40.20.200-10.40.20.249 (total=50, used=0); DHCPOFFER-Probe auf vmbr0.20 -> 10.40.20.200 mit Domain Name Server 10.40.20.30, 10.40.20.31; Probe-Lease wieder entfernt; Compliance 140/140 ohne Drift. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Nautobot-Tooling
ingest-baseline.py
Idempotenter Basisdaten-Ingest (Locations, Rollen, Device-Types, Devices, VLANs/Prefixes, ap1-Interfaces + IPs, WLAN-Modelle). Bildet den Ist-Zustand ab, nicht den Zielzustand.
export NAUTOBOT_TOKEN=... # aus docs/nas-vlan20/stacks/210-nautobot.yml (gitignored)
python3 nautobot/ingest-baseline.py # Dry-Run
python3 nautobot/ingest-baseline.py --apply # schreiben
API-Fallen (teuer gelernt)
tagged_vlansliest sich per REST alsnull, obwohl korrekt gespeichert. Verifikation nur über GraphQL:POST /api/graphql/mit{interfaces(device:"ap1"){name mode untagged_vlan{vid} tagged_vlans{vid}}}.- Relationen kommen ohne
?depth=1ohnedisplay→ ein voll gepflegtes Device sieht leer aus. LocationType.content_typesmussipam.vlangroup/ipam.vlan/ipam.prefix/dcim.controllerenthalten, sonst 400 beim Anlegen.parent_interfacenur für virtuelle Interfaces — WLAN-VAPs können ihren Master nicht als Parent tragen.WirelessNetworkhat kein VLAN-Feld; die VLAN-Bindung hängt anwireless/controller-managed-device-group-wireless-network-assignmentsund braucht zwingend einenController(bei uns logisch,controller_deviceleer — kein CAPsMAN).RadioProfile.channel_widthwill Integer ([20,40,80]), nicht"20mhz".- IP↔Interface läuft über
/ipam/ip-address-to-interface/.
ingest-wifi-access-list-rule.py
Legt ComplianceFeature + ComplianceRule wifi-access-list an (Plattform RouterOS,
match_config=/interface wifi access-list). Idempotent, Dry-Run ist Standard.
export NAUTOBOT_TOKEN=...
python3 nautobot/ingest-wifi-access-list-rule.py --apply
Warum es die Rule braucht: ohne sie erzeugen Eintraege in der access-list keinen Drift
und lassen sich nicht als Config-Transaktion fuehren -- gc-transaktion.py verlangt eine
ComplianceRule als --scope. docs/tp25-pin-s22.sh schreibt dort bei jedem Messrundgang, und
TP25 Fund E (signal-range-Boden) landet dauerhaft dort.
Der Soll-Zustand ist leer und steht als Kommentarblock [6c] im Template. Waehrend eines
Messlaufs ist der Drift erwartet und gehoert als offene Transaktion gefuehrt.