TP15: Portfakten + Governance-Ausnahme CRS326-Bring-up + Handoff-Prompt
Portplan-Widerspruch aufgeloest (User): Core #15 = ap1 (Ziel kuenftig #4), #11 = cudy1. Damit gilt die TP4-Variante (gi16 Arris -> gi15 Trunk -> ap1); 03-trunk-ap-ports.cli (gi1 = ap1) ist STALE, vom Edge-Cutover 2026-07-01 ueberholt. gi16/gi8 bleiben unverifiziert und sind in Nautobot als solche markiert. Kernrisiko dokumentiert: crs326-core ersetzt sw1 UND wird L3-Router, an sw1 haengt ap1 (#15) mit Gateway/DHCP/DNS/WAN-Trunk -> Core-Tausch und L3-Umzug sind dasselbe Ereignis und bedeuten ein Vollausfall-Fenster. Entscheid: Branch zuerst, Core eigene Session. Rettungspfade liegen guenstig getrennt (Notebook-WLAN via cudy1 am Core #11, Kabel-Plan-B am sw2/Branch P4). Governance-Ausnahme #2 erteilt (User): CRS326-Grundconfig direkt via MikroMCP/Winbox (Henne-Ei - GC kann nicht auf ein Geraet ohne IP pushen, Templates fehlen). Auflage: Ist-Stand danach als intended config nach Nautobot. Gilt nicht fuer ap1/ap2. Nautobot: cudy1/cudy2 als Cudy WR3600E V1.0 (Firmware 2.3.15-20250905-191956), cudy1 -> Technik UG, cudy2 -> Technik OG/Decommissioning; Platzhalter-DeviceType entfernt; sw1 gi8/gi11/gi15/gi16; crs326-core ether4 als Zielport fuer ap1. Alle 8 Devices haben jetzt einen Bereich. Neu: docs/tp15-crs326-session-handoff-prompt.md - kopierfertiger Auftrag fuer die naechste Session (beide CRS326 bring-up, Portplan, NUR Branch-Tausch). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -29,6 +29,7 @@ Kontrolle: MikroMCP (MikroTik) live · ALLNET = Klickanleitungen · Omada → Co
|
||||
> Folge: **native RouterOS-Features bevorzugen** (z.B. Built-in-mDNS statt Container); Container (mDNS-Reflector, BIND9-DNS, Omada-Controller) laufen auf **NAS/pve**, nicht am hAP.
|
||||
> 🔒 **Governance-Entscheid (User, 2026-07-16, ausgeweitet 2026-07-16): Config ausschließlich über Nautobot — keine Direkt-Changes mehr an einzelnen im Netz verstreuten Geräten.** Ursprünglich als ap1/ap2-Config-Freeze bis TP21 formuliert, noch am selben Tag verallgemeinert: **jede** Netz-Gerätekonfiguration (Router, APs, künftig auch die CRS326-Switches) läuft ab jetzt exklusiv über Nautobot Golden Config (TP21), sobald das steht — keine ad-hoc MikroMCP-Writes/UI-Klicks mehr an Einzelgeräten. **TP19.1 war die letzte Ausnahme** (ap1-DNS-Fix + ap2/sw1/sw2-Mgmt-VLAN-Umzug, s.u.) — davon ist nur der risikolose ap1-Teil noch direkt gemacht worden, ap2 wurde bewusst auf „nach Nautobot" vertagt. **Macht Nautobot + alle Prerequisites (TP18→TP20→TP20.1→TP21, jetzt auch Komodo) zum aktuell kritischen Pfad**, vor TP5/TP6. Betrifft nicht Software-Migrationen auf NAS/pve (anderes Substrat) und die Frage, ob sw1/sw2 vor der CRS326-Ablösung noch angefasst werden (offen, s. TP15/TP19.1).
|
||||
> ✅ **Genehmigte Ausnahme (User, 2026-07-17): der zentrale CF-Tunnel-Zugang auf ap1 (TP16/TP16.1).** Alle für den zentralen Cloudflare-Zugang **auf ap1** notwendigen (Vor-)Arbeiten dürfen direkt via MikroMCP/CF-API gemacht werden, ohne auf Nautobot GC zu warten — konkret die nativen WireGuard-Mesh-Zeilen aus TP16.1 (und analoge cloudflared/WG-Anpassungen am ap1-Edge). Begründung: Remote-Erreichbarkeit ist die Voraussetzung, um überhaupt sicher am Netz zu arbeiten (Henne-Ei zu Nautobot). **Scope-Grenze:** Die Ausnahme gilt **nur für ap1**. Sobald der zentrale Zugang später auf die **CRS326** wandert (TP15), gilt die Ausnahme dafür **vorerst NICHT** — CRS326-Config läuft dann regulär über Nautobot GC (oder braucht eine eigene, dann neu zu erteilende Ausnahme). Nachtrag der ap1-Ausnahmezeilen in die GC als Follow-up, sobald Nautobot steht.
|
||||
> ✅ **Genehmigte Ausnahme #2 (User, 2026-07-22): der CRS326-Bring-up (TP15).** Die **Grundkonfiguration** beider CRS326 — Management-IP, Bridge, VLAN-Filtering, Trunks/Access-Ports — darf **direkt** via MikroMCP/Winbox gemacht werden. **Begründung = Henne-Ei:** Golden Config kann nicht auf ein Gerät pushen, das im Werkszustand keine erreichbare IP hat, und die GC-Templates existieren noch nicht (TP21 „offen: Templates/intended config"). **Auflage:** der so entstandene Ist-Stand wird anschließend als **intended config nach Nautobot zurückgeschrieben**, danach übernimmt GC — die Ausnahme deckt das Bootstrapping, nicht den Dauerbetrieb. **Scope-Grenze:** gilt für die beiden CRS326 im Bring-up. Sie hebt die Regel für ap1/ap2 **nicht** auf (deren Neuprovisionierung läuft weiterhin über GC) und verlängert sich nicht automatisch auf spätere CRS326-Änderungen. Damit ist die frühere Formulierung in TP16.1 („CRS326-Config läuft regulär über Nautobot GC oder braucht eine eigene, dann neu zu erteilende Ausnahme") **erfüllt** — dies ist diese Ausnahme.
|
||||
|
||||
---
|
||||
|
||||
@@ -362,11 +363,16 @@ FT kaschierte das bisher als „Roaming" statt „schwache Abdeckung".
|
||||
| Bereich | heute | wird abgelöst durch |
|
||||
|---|---|---|
|
||||
| **Technik UG** (virtueller Bereich in der Diele, **kein Keller**) | sw1 (ALLNET, 16 Ports) | **crs326-core** (= neuer L3-Router) |
|
||||
| **Technik OG** | sw2 (ES208G, 8 Ports) + ap2 | **crs326-branch** |
|
||||
| **Technik OG** | sw2 (ES208G, 8 Ports) + ap2 + cudy2 | **crs326-branch** |
|
||||
| **Wohnzimmer Sideboard** | ap1 | — (bleibt, wird reiner AP) |
|
||||
Kapazität unkritisch: 16+8 belegte Ports heute gegen 2× 24 GbE + 2× SFP+ künftig. In Nautobot gepflegt. `cudy1` hat noch keinen Bereich.
|
||||
Kapazität unkritisch: 16+8 belegte Ports heute gegen 2× 24 GbE + 2× SFP+ künftig. Vollständig in Nautobot gepflegt (alle 8 Devices haben einen Bereich).
|
||||
- **📻 cudy-Hardware (User 2026-07-22):** beide sind **Cudy WR3600E V1.0**, Firmware `2.3.15-20250905-191956`. `cudy1` = Technik UG, am Core `#11`, aktiv als Übergangs-WLAN (`elektrosmog.c`, `10.40.10.246`) — **und Christophs aktueller WLAN-Pfad**. `cudy2` = Technik OG, außer Betrieb (Nautobot-Status `Decommissioning`), wird nicht reaktiviert.
|
||||
- ⚠️ **Etappe 1 (Erstzugriff auf die CRS326) ist NICHT kritisch** (User 2026-07-22): Werkszustand, Gerät steht griffbereit neben ihm → der IP-Konflikt auf `192.168.88.1` wird physisch/seriell aufgelöst, kein Lockout-Szenario.
|
||||
- 🚧 **Blocker für den Portplan: sw1s Ist-Belegung ist nirgends verlässlich erfasst.** `switch/state-backups/sw1-running-config-20260716-*.txt` sind 214-Byte-Fehlschläge (`Incorrect User Name!!` — toter `claudecode`-Login), `switch/cli/running-PRE.txt` ist der Stand **vor** TP1. Aus den angewendeten CLI-Dateien + Doku ergibt sich ein **Widerspruch**: `03-trunk-ap-ports.cli` sagt **gi1 = ap1 / gi2 = ap2**, der WAN-Cutover in TP4 sagt **gi16 = Arris Access VLAN100 → gi15 Trunk → ap1 ether2**. Ungeklärt, ob ap1 von gi1 auf gi15 umgezogen ist oder gi15 ein zweiter Link ist. **Muss vom Gerät gelesen werden** (User macht sw1 ohnehin selbst im UI/CLI), bevor der CRS326-Portplan entstehen kann.
|
||||
- ✅ **Portplan-Widerspruch AUFGELÖST (User 2026-07-22): am Core ist `#15 = ap1` und `#11 = cudy1`.** Damit gilt die TP4-Variante (`gi16` Arris/VLAN100 → `gi15` Trunk → ap1 `ether2`); **`switch/cli/03-trunk-ap-ports.cli` (gi1 = ap1 / gi2 = ap2) ist STALE** — die Datei bezeichnet sich selbst als „provisorisch, 2026-06-09" und wurde vom Edge-Cutover am 2026-07-01 überholt. Nicht mehr als Quelle verwenden.
|
||||
- **Zielport nach dem Tausch: ap1 wandert auf `crs326-core` Port 4** (User-Vorgabe). Gleichzeitig wandert ap1s Uplink geräteseitig von `ether2` auf `ether1` (PoE-Port, s.o.).
|
||||
- ⚠️ **Nach wie vor NICHT vom Gerät gelesen:** die restliche sw1-Belegung. `switch/state-backups/sw1-running-config-20260716-*.txt` sind 214-Byte-Fehlschläge (`Incorrect User Name!!`, toter `claudecode`-Login), `switch/cli/running-PRE.txt` ist Pre-TP1. `gi16` (Arris) und `gi8` (Trunk → sw2) stehen in Nautobot daher **explizit als „UNVERIFIZIERT"** markiert.
|
||||
- 🔴 **Betriebliches Kernrisiko (Analyse 2026-07-22): der Core-Tausch ist KEIN Switch-Tausch, sondern ein Vollausfall.** `crs326-core` ersetzt sw1 **und** wird gleichzeitig L3-Router — und an sw1 hängt auf `#15` ap1, also Gateway, DHCP, DNS und der WAN-Trunk fürs gesamte Netz. Core-Tausch und L3-Umzug sind damit **faktisch dasselbe Ereignis** und brauchen ein geplantes Ausfallfenster. **→ Reihenfolge-Entscheid (User 2026-07-22): `crs326-branch` zuerst (gegen sw2, Technik OG), `crs326-core` in einer eigenen, späteren Session.**
|
||||
- 🛟 **Rettungspfade liegen günstig auf verschiedenen Switches:** Christophs Notebook hängt per **WLAN über cudy1 → Core `#11`**; der kabelgebundene Plan B (Dock/Monitor Arbeitszimmer) hängt am **sw2/Branch (P4)**. Beim **Branch**-Tausch stirbt also der Kabelweg, das WLAN überlebt. (Beim Core-Tausch stirbt beides — s. Kernrisiko oben.)
|
||||
- **Hinweis:** die A/B/C/D-Switch-Varianten in `hw-refresh-requirements.md` sind durch die CRS326-Beschaffung überholt (Doc = Recherche-Archiv + USV/Energie-Referenz).
|
||||
- Abh.: TP14 ✅ (IP-Migration). Verzahnt: **TP20** (Platform-DB), **TP21** (Nautobot GC), **TP2.1** (sw2-Standalone-Recovery).
|
||||
|
||||
|
||||
126
docs/tp15-crs326-session-handoff-prompt.md
Normal file
126
docs/tp15-crs326-session-handoff-prompt.md
Normal file
@@ -0,0 +1,126 @@
|
||||
# TP15 — Handoff-Prompt: CRS326-Bring-up + Branch-Tausch (neue Session)
|
||||
|
||||
> **Zweck:** Kopierfertiger Auftrag für eine frische Session, die die beiden CRS326 in Betrieb
|
||||
> nimmt und **crs326-branch** produktiv gegen sw2 tauscht. Der Core-Tausch (= gleichzeitig der
|
||||
> L3-Umzug) ist **bewusst NICHT** Teil dieses Auftrags.
|
||||
>
|
||||
> **Empfohlenes Modell:** Opus. Nicht wegen der Befehle — die sind Standard-RouterOS — sondern
|
||||
> weil in der Session laufend zwischen „Ist-Zustand am Gerät", „Modell in Nautobot" und
|
||||
> „Zielbild" abgeglichen werden muss und die sw1-Belegung nur teilweise verifiziert ist.
|
||||
>
|
||||
> **So übergibst du es:** Neue Session öffnen, als ersten Prompt schicken:
|
||||
> „Lies `docs/tp15-crs326-session-handoff-prompt.md` und arbeite den Auftrag darin ab."
|
||||
|
||||
--- PROMPT ---
|
||||
|
||||
```
|
||||
TP15 — CRS326 in Betrieb nehmen: beide grundkonfigurieren, Portplan in Nautobot
|
||||
modellieren, danach NUR crs326-branch produktiv gegen sw2 tauschen.
|
||||
|
||||
KONTEXT ZUERST LESEN (in dieser Reihenfolge):
|
||||
1. docs/teilprojekte.md — TP15 (Hardware/Standorte/Portfakten/Risiko), TP21 (Nautobot-Stand),
|
||||
TP3 (WLAN-Neuaufbau ohne CAPsMAN) + die beiden Governance-Callouts im Kopfbereich.
|
||||
2. CLAUDE.md (Projekt) — Hardware/Zugänge/Gotchas.
|
||||
3. nautobot/README.md — die Nautobot-API-Fallen. NICHT überspringen: `tagged_vlans` liest
|
||||
sich per REST als `null`, OBWOHL korrekt gespeichert. Wer das nicht weiß, sucht eine
|
||||
Stunde einen Fehler, den es nicht gibt. Verifikation immer per GraphQL.
|
||||
4. docs/tp16.1-warp-mesh-native-crs326.md — was später auf dem Core terminieren soll.
|
||||
|
||||
ZIEL DIESER SESSION (Scope-Grenze hart einhalten):
|
||||
(a) beide CRS326 grundkonfigurieren (Mgmt-IP, Bridge, vlan-filtering, Trunks/Access),
|
||||
(b) den Portplan beider Switches in Nautobot modellieren,
|
||||
(c) crs326-branch physisch gegen sw2 tauschen (Technik OG).
|
||||
NICHT in dieser Session: crs326-core produktiv setzen, L3-Umzug (Routing/DHCP/DNS von ap1),
|
||||
CAPsMAN-Abbau, ap2-Neuprovisionierung. Das sind eigene, spätere Sessions.
|
||||
|
||||
‼️ WARUM DER CORE TABU IST — DAS IST KEINE VORSICHT, DAS IST DIE TOPOLOGIE ‼️
|
||||
crs326-core ersetzt sw1 UND wird der neue L3-Router. An sw1 hängt auf Port 15 ap1 — also
|
||||
Gateway, DHCP, DNS und der WAN-Trunk fürs GESAMTE Netz. Wer sw1 anfasst, nimmt das ganze
|
||||
Haus offline, inklusive des eigenen Management-Pfads. Core-Tausch und L3-Umzug sind faktisch
|
||||
dasselbe Ereignis und brauchen ein geplantes Ausfallfenster. Deshalb: Branch zuerst.
|
||||
|
||||
GOVERNANCE — AUSNAHME BEREITS ERTEILT (Christoph, 2026-07-22):
|
||||
Die Regel "Config ausschließlich über Nautobot" gilt weiter, ABER für den CRS326-BRING-UP
|
||||
hat Christoph eine Ausnahme erteilt: Mgmt-IP, Bridge, VLAN-Filtering, Trunks/Access-Ports
|
||||
dürfen direkt via MikroMCP/Winbox gesetzt werden (Henne-Ei: GC kann nicht auf ein Gerät ohne
|
||||
erreichbare IP pushen, und die GC-Templates existieren noch nicht).
|
||||
AUFLAGE: den entstandenen Ist-Stand danach als intended config nach Nautobot zurückschreiben.
|
||||
Die Ausnahme deckt das Bootstrapping, NICHT den Dauerbetrieb, und gilt NICHT für ap1/ap2.
|
||||
|
||||
GERÄTE-IDENTITÄT (beide sind bereits am Netz, RouterOS 7.19.5, Werkszustand):
|
||||
crs326-core = MAC 04:F4:1C:B4:17:76, software-id 4W04-URHT
|
||||
crs326-branch = MAC 04:F4:1C:B4:15:58, software-id RGZJ-UK0T
|
||||
⚠️ BEIDE hören auf die Werks-IP 192.168.88.1 im SELBEN L2-Segment = IP-Konflikt.
|
||||
Immer nur eines nach dem anderen adressieren; Unterscheidung ausschließlich per MAC
|
||||
(MAC-Telnet/Winbox-Neighbor). Christoph hat die Geräte griffbereit neben sich, der
|
||||
Erstzugriff ist deshalb unkritisch — kein Lockout-Szenario, notfalls physisch.
|
||||
⚠️ crs326-branch ist BEREITS PHYSISCH INLINE: ap2 hängt an dessen Port 24 (deshalb war es
|
||||
überhaupt per MNDP von ap1 aus sichtbar). Das ist kein Greenfield-Einbau.
|
||||
|
||||
ZIELBILD BRANCH = die heutige sw2-Belegung (aus TP2) 1:1 übernehmen:
|
||||
P1 Uplink-Trunk (→ sw1 gi8) | P2 ap2 | P3 frei | P4 Dock/Monitor | P5 Drucker
|
||||
P6 frei | P7 Mac mini | P8 NAS (2. Port)
|
||||
⚠️ P4/P7/P8 sind in TP2 ausdrücklich als "nicht anfassen" markiert (fixe IPs, Lifeline).
|
||||
VLANs laut kanonischem Modell: Uplink+ap2 = Trunk all; Rest Access auf dem jeweiligen VLAN.
|
||||
|
||||
CONNECTIVITY — VOR DEM KABELZIEHEN LESEN:
|
||||
Christophs Notebook hängt aktuell NUR per WLAN über cudy1, und cudy1 hängt am CORE (Port 11).
|
||||
Der kabelgebundene Plan B (Dock/Monitor im Arbeitszimmer) hängt am sw2/BRANCH (P4).
|
||||
Beim Branch-Tausch stirbt also der Kabelweg, das WLAN über cudy1 überlebt — die Pfade liegen
|
||||
günstig auf verschiedenen Switches. Trotzdem: vor dem Ziehen von sw2 ansagen.
|
||||
|
||||
WAS VERIFIZIERT IST UND WAS NICHT (nicht raten, nicht als Fakt ausgeben):
|
||||
✅ sw1 #15 = ap1, sw1 #11 = cudy1 (von Christoph bestätigt 2026-07-22)
|
||||
⚠️ sw1 gi16 (Arris/VLAN100) und gi8 (Trunk → sw2) stammen aus der Doku, sind NICHT am
|
||||
Gerät gelesen — in Nautobot als "UNVERIFIZIERT" markiert. Falls für den Branch-Uplink
|
||||
relevant: von Christoph bestätigen lassen, er macht sw1 ohnehin selbst.
|
||||
❌ switch/cli/03-trunk-ap-ports.cli (gi1 = ap1) ist STALE — vom Edge-Cutover 2026-07-01
|
||||
überholt. Nicht als Quelle verwenden.
|
||||
❌ switch/state-backups/sw1-running-config-*.txt sind 214-Byte-Fehlschläge (toter
|
||||
claudecode-Login), switch/cli/running-PRE.txt ist Pre-TP1.
|
||||
|
||||
NAUTOBOT (SoT, http://10.40.20.30:8080):
|
||||
Token: NAUTOBOT_SUPERUSER_API_TOKEN in docs/nas-vlan20/stacks/210-nautobot.yml (gitignored).
|
||||
Bereits gepflegt: 8 Devices mit Bereich/Rolle/Platform, 9 VLANs, 8 Prefixes, ap1 vollständig
|
||||
(26 Interfaces + IPs), 6 SSIDs mit VLAN-Bindung, sw1 gi8/gi11/gi15/gi16, crs326-core ether4.
|
||||
Wiederverwenden: nautobot/ingest-baseline.py (idempotentes goc()/patch()-Muster) —
|
||||
nicht neu erfinden. Neue Ports als Interfaces am jeweiligen Device anlegen.
|
||||
|
||||
ROUTEROS-FAKTEN, DIE ZEIT SPAREN:
|
||||
- Interface-Nummerierung beginnt bei 1 (ether1…ether24). Es gibt KEIN ether0.
|
||||
- hAP ax²: PoE-Port ist ether1, aber PASSIV 18–28 V, NICHT 802.3af/at. Der CRS326 hat gar
|
||||
kein PoE → Strom käme aus einem Inline-Injektor. ether1 am hAP steht auf poe-out=auto-on;
|
||||
wenn ether1 künftig der versorgte Uplink wird, muss poe-out dort auf off.
|
||||
(PoE ist laut Christoph "vorerst kein Thema", die Config soll es nur vorsehen.)
|
||||
- MikroMCP kennt in routers.yaml nur ap1/ap2 → die CRS326 dort ergänzen, sonst kein
|
||||
MCP-Zugriff. Writes immer erst dryRun/plan_changes, dann apply_plan.
|
||||
|
||||
NICHT ANFASSEN:
|
||||
- ap1-Config (der CAPsMAN-Abbau kommt erst NACH dem Core-Tausch, s. TP3).
|
||||
- ap2 (aktuell unerreichbar, meldet 10.40.40.2 = falsches VLAN; wird ohnehin neu
|
||||
provisioniert — nicht diagnostizieren, nicht reparieren).
|
||||
- sw1 (Christoph macht sw1 selbst; siehe Feedback-Memory).
|
||||
|
||||
ABNAHME:
|
||||
- Beide CRS326 unter ihrer neuen Mgmt-IP erreichbar, per MAC eindeutig zugeordnet, IP-Konflikt
|
||||
aufgelöst.
|
||||
- Nach dem Branch-Tausch: ap2-Port, Dock/Monitor, Drucker, Mac mini und NAS wieder erreichbar;
|
||||
jeder in seinem korrekten VLAN (nicht ungetaggt in VLAN1 — das ist die Fehlerklasse, die
|
||||
dieses Projekt seit Wochen verfolgt).
|
||||
- Nautobot bildet die tatsächliche Portbelegung ab; Verifikation per GraphQL, nicht REST.
|
||||
|
||||
DOKU (Pflicht am Ende): teilprojekte.md TP15 fortschreiben (As-built Portplan, was tatsächlich
|
||||
lief/nicht lief), Nautobot-Stand nachziehen, Gotchas festhalten. Danach committen + pushen
|
||||
(Commit-Msg-Konvention siehe git log: deutsch, Bullet-Struktur, Co-Authored-By).
|
||||
```
|
||||
|
||||
--- ENDE PROMPT ---
|
||||
|
||||
## Was Christoph zur Session parat haben sollte
|
||||
- **Beide CRS326 griffbereit** (Erstzugriff/IP-Konflikt wird physisch aufgelöst).
|
||||
- **Bestätigung der sw1-Ports `gi16` und `gi8`**, falls der Branch-Uplink daran hängt —
|
||||
aktuell nur aus der Doku, nicht vom Gerät gelesen.
|
||||
- **Zeitfenster für den sw2-Ausfall** (Dock/Monitor, Drucker, Mac mini, NAS-2. Port sind
|
||||
währenddessen weg; WLAN über cudy1 bleibt).
|
||||
- Klarheit, ob der **NAS-2.-Port (sw2 P8)** und der **Mac mini (P7)** während des Tauschs
|
||||
ausfallen dürfen — beide sind in TP2 als „nicht anfassen" markiert.
|
||||
133
nautobot/ingest-2026-07-22-restdaten.py
Normal file
133
nautobot/ingest-2026-07-22-restdaten.py
Normal file
@@ -0,0 +1,133 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Restdaten-Ingest 2026-07-22: cudy1/cudy2, sw1-Ports, crs326-core Zielport."""
|
||||
import json, os, sys, urllib.parse, urllib.request, urllib.error
|
||||
|
||||
BASE = "http://10.40.20.30:8080/api"
|
||||
TOKEN = os.environ["NAUTOBOT_TOKEN"]
|
||||
DRY = "--apply" not in sys.argv
|
||||
log = []
|
||||
|
||||
|
||||
def req(method, path, data=None):
|
||||
url = path if path.startswith("http") else f"{BASE}{path}"
|
||||
body = json.dumps(data).encode() if data is not None else None
|
||||
r = urllib.request.Request(url, data=body, method=method)
|
||||
r.add_header("Authorization", f"Token {TOKEN}")
|
||||
r.add_header("Content-Type", "application/json")
|
||||
r.add_header("Accept", "application/json")
|
||||
try:
|
||||
with urllib.request.urlopen(r, timeout=30) as resp:
|
||||
raw = resp.read()
|
||||
return json.loads(raw) if raw else {}
|
||||
except urllib.error.HTTPError as e:
|
||||
print(f"!! {method} {url}\n {json.dumps(data)}\n -> {e.code} {e.read().decode()[:400]}")
|
||||
raise
|
||||
|
||||
|
||||
def find(path, **params):
|
||||
q = "&".join(f"{k}={urllib.parse.quote(str(v))}" for k, v in params.items())
|
||||
res = req("GET", f"{path}?{q}&limit=2")
|
||||
return res["results"][0] if res.get("count") else None
|
||||
|
||||
|
||||
def goc(path, match, payload, label=None):
|
||||
name = label or payload.get("name") or payload.get("model")
|
||||
obj = find(path, **match)
|
||||
if obj:
|
||||
log.append(f" = {name}")
|
||||
return obj
|
||||
if DRY:
|
||||
log.append(f" + {name} (dry)")
|
||||
return {"id": f"DRY-{name}"}
|
||||
log.append(f" + {name}")
|
||||
return req("POST", path + "/", payload)
|
||||
|
||||
|
||||
def patch(path, obj, payload, label):
|
||||
delta = {}
|
||||
for k, v in payload.items():
|
||||
cur = obj.get(k)
|
||||
cur = cur.get("id") if isinstance(cur, dict) else cur
|
||||
if cur != v:
|
||||
delta[k] = v
|
||||
if not delta:
|
||||
log.append(f" = {label} (unveraendert)")
|
||||
return obj
|
||||
if DRY:
|
||||
log.append(f" ~ {label}: {list(delta)} (dry)")
|
||||
return obj
|
||||
log.append(f" ~ {label}: {list(delta)}")
|
||||
return req("PATCH", f"{path}/{obj['id']}/", delta)
|
||||
|
||||
|
||||
sid = lambda n: find("/extras/statuses", name=n)["id"]
|
||||
S_ACTIVE, S_DECOM = sid("Active"), sid("Decommissioning")
|
||||
FW = "Cudy WR3600E V1.0, Firmware 2.3.15-20250905-191956."
|
||||
|
||||
print("== 1. Cudy: echter DeviceType statt Platzhalter")
|
||||
cudy_mfr = find("/dcim/manufacturers", name="Cudy")
|
||||
dt_new = goc("/dcim/device-types", {"model": "WR3600E V1.0"}, {
|
||||
"manufacturer": cudy_mfr["id"], "model": "WR3600E V1.0", "u_height": 0,
|
||||
"is_full_depth": False, "comments": "Cudy WR3600E V1.0 — Uebergangs-WLAN waehrend der Umstellung."})
|
||||
|
||||
LOC = {n: find("/dcim/locations", name=n) for n in ("Technik UG", "Technik OG")}
|
||||
role_ap = find("/extras/roles", name="Wireless AP")
|
||||
|
||||
print("== 2. cudy1 / cudy2")
|
||||
c1 = find("/dcim/devices", name="cudy1")
|
||||
patch("/dcim/devices", c1, {
|
||||
"device_type": dt_new["id"], "location": LOC["Technik UG"]["id"], "status": S_ACTIVE}, "cudy1")
|
||||
if not DRY:
|
||||
req("PATCH", f"/dcim/devices/{c1['id']}/", {"comments":
|
||||
f"{FW} Standalone-WLAN 'elektrosmog.c' auf 10.40.10.246, haengt an sw1 Port 11. "
|
||||
"Uebergangssender, kein Abbauziel mehr (TP19). WICHTIG: Christophs Notebook haengt "
|
||||
"per WLAN hierueber — Rettungspfad beim Branch-Tausch."})
|
||||
|
||||
goc("/dcim/devices", {"name": "cudy2"}, {
|
||||
"name": "cudy2", "device_type": dt_new["id"], "role": role_ap["id"],
|
||||
"location": LOC["Technik OG"]["id"], "status": S_DECOM,
|
||||
"comments": f"{FW} Ausser Betrieb, wird nicht reaktiviert (TP19, ARP-Sweep bestaetigt "
|
||||
"keine Praesenz mehr). Frueher 10.40.41.247."})
|
||||
|
||||
print("== 3. Platzhalter-DeviceType entfernen")
|
||||
old = find("/dcim/device-types", model="Cudy AP (Modell TBD)")
|
||||
if old and not DRY:
|
||||
if req("GET", f"/dcim/devices/?device_type={old['id']}&limit=1")["count"] == 0:
|
||||
req("DELETE", f"/dcim/device-types/{old['id']}/")
|
||||
log.append(" - Cudy AP (Modell TBD) geloescht")
|
||||
else:
|
||||
log.append(" ! Platzhalter noch in Benutzung — nicht geloescht")
|
||||
|
||||
print("== 4. sw1-Ports (nur Gesichertes + markiert Unverifiziertes)")
|
||||
sw1 = find("/dcim/devices", name="sw1")
|
||||
V = {v: find("/ipam/vlans", vid=v) for v in (1, 10, 20, 30, 40, 50, 90, 99, 100)}
|
||||
PORTS = [
|
||||
("gi15", "ap1-Trunk (bestaetigt User 2026-07-22). Ziel: wandert auf crs326-core ether4.",
|
||||
"tagged", 1, [10, 20, 30, 40, 50, 90, 99, 100]),
|
||||
("gi11", "cudy1 (bestaetigt User 2026-07-22). Christophs WLAN-Pfad haengt hier dran.",
|
||||
None, None, None),
|
||||
("gi16", "Arris-Kabelmodem, Access VLAN100 (aus TP4-Doku, UNVERIFIZIERT am Geraet).",
|
||||
"access", 100, None),
|
||||
("gi8", "Trunk zu sw2 (aus TP1/TP2-Doku, UNVERIFIZIERT am Geraet).",
|
||||
"tagged", 1, [10, 20, 30, 40, 50, 90, 99, 100]),
|
||||
]
|
||||
for nm, desc, mode, untag, tags in PORTS:
|
||||
i = goc("/dcim/interfaces", {"device": "sw1", "name": nm}, {
|
||||
"device": sw1["id"], "name": nm, "type": "1000base-t",
|
||||
"status": S_ACTIVE, "description": desc}, label=f"sw1:{nm}")
|
||||
if not DRY and mode:
|
||||
p = {"mode": mode, "untagged_vlan": V[untag]["id"]}
|
||||
if tags:
|
||||
p["tagged_vlans"] = [V[t]["id"] for t in tags]
|
||||
req("PATCH", f"/dcim/interfaces/{i['id']}/", p)
|
||||
|
||||
print("== 5. crs326-core: Zielport fuer ap1")
|
||||
core = find("/dcim/devices", name="crs326-core")
|
||||
goc("/dcim/interfaces", {"device": "crs326-core", "name": "ether4"}, {
|
||||
"device": core["id"], "name": "ether4", "type": "1000base-t", "status": S_ACTIVE,
|
||||
"description": "Ziel-Port fuer ap1 (heute sw1 #15). User-Vorgabe 2026-07-22. "
|
||||
"ap1-Uplink wandert dabei von ether2 auf ether1 (PoE-Port)."},
|
||||
label="crs326-core:ether4")
|
||||
|
||||
print("\n".join(log))
|
||||
print("\n" + ("DRY-RUN — nichts geschrieben." if DRY else "ANGEWENDET."))
|
||||
Reference in New Issue
Block a user