TP3-Rebuild: ap1+ap2 als reine Standalone-APs (CAPsMAN weg)
Beide hAP ax² neu als Standalone-Access-Points provisioniert, kein CAPsMAN mehr: - ap1 10.40.99.11 (core ether15), ap2 10.40.99.12 (branch ether24) - Uplink geraeteseitig auf ether1 (PoE-Port, poe-out=off); ether2 aus Trunk entfernt - 6 SSIDs (main WPA3+FT; infra/iot/media/untrusted/guest WPA2+3), iot nur 2,4 GHz, media FT=no; jede SSID mit eigenem datapath bridge+vlan-id -> TP3.1-Leck strukturell weg - Kanaele non-DFS: ap1 ch36/ch1, ap2 ch149/ch11 - Einheitliches Admin-PW auf allen 4 MikroTik (core/branch/ap1/ap2) Nautobot As-built (nautobot/ingest-ap-rebuild.py): ap1/ap2 Rolle Wireless AP + Active, Mgmt .99.11/.99.12, ether1-Trunk, stale L3-Objekte (vlan10-90/100, wg-hub) entfernt; crs326-core primary_ip4 auf .99.1 korrigiert. Verifiziert per GraphQL. Doku: CLAUDE.md Hardware-Tabelle + teilprojekte.md TP3 aktualisiert. Configs/PSKs liegen gitignored unter routeros/.secrets/. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -31,8 +31,9 @@ GW je VLAN = `.1` · APs: ap1=`.1`, ap2=`.2` · Switches: ALLNET=`.11`, Omada=`.
|
|||||||
| ~~Omada Access-Switch (sw2)~~ | — | ❌ **abgebaut 2026-07-23** (TP15 Branch-Tausch) — durch `crs326-branch` in Technik OG ersetzt, physisch entfernt |
|
| ~~Omada Access-Switch (sw2)~~ | — | ❌ **abgebaut 2026-07-23** (TP15 Branch-Tausch) — durch `crs326-branch` in Technik OG ersetzt, physisch entfernt |
|
||||||
| **crs326-branch** (CRS326, Technik OG) | **10.40.99.2** (VLAN99; temp VLAN10 `.10.2`) · Bootstrap Rev4 2026-08-08 | RouterOS 7.19.5 · `admin`/`~/.secrets` (`CRS326_BRANCH_*`) · REST `http://10.40.99.2/rest` · MikroMCP `routers.yaml` (live nach MCP-Neustart) · **TP15 ✅: ersetzt sw2** (L2-Switch), volle Portbelegung (ether1 Uplink-Trunk, 2/8 NAS, 3 Drucker, 4 Dock, 5/6 Sonos, 7 macmini, 24 ap2) · sw-id RGZJ-UK0T, MAC 04:F4:1C:B4:15:58 |
|
| **crs326-branch** (CRS326, Technik OG) | **10.40.99.2** (VLAN99; temp VLAN10 `.10.2`) · Bootstrap Rev4 2026-08-08 | RouterOS 7.19.5 · `admin`/`~/.secrets` (`CRS326_BRANCH_*`) · REST `http://10.40.99.2/rest` · MikroMCP `routers.yaml` (live nach MCP-Neustart) · **TP15 ✅: ersetzt sw2** (L2-Switch), volle Portbelegung (ether1 Uplink-Trunk, 2/8 NAS, 3 Drucker, 4 Dock, 5/6 Sonos, 7 macmini, 24 ap2) · sw-id RGZJ-UK0T, MAC 04:F4:1C:B4:15:58 |
|
||||||
| **crs326-core** (CRS326, Technik UG) | **10.40.99.1** (VLAN99-SVI = L3-GW; same-subnet-Fallback `10.40.10.1`) · Identity `crs326-core` | RouterOS 7.19.5 · `admin`/`~/.secrets` (`CRS326_CORE_*`) · MikroMCP routerId `crs326-core` (nach MCP-Neustart) · **TP15/TP21.1 ✅ 2026-08-08: PRODUKTIVER L3-Router** (ersetzt ap1) — Routing/DHCP (7 Server + 54 Leases)/DNS (plain `1.1.1.1`, DoH-Cloudflare-TODO)/NAT/WG-Hub (fm+dw live); WAN `vlan100`=Liwest `90.146.33.177` · sw-id 4W04-URHT, MAC 04:F4:1C:B4:17:76 |
|
| **crs326-core** (CRS326, Technik UG) | **10.40.99.1** (VLAN99-SVI = L3-GW; same-subnet-Fallback `10.40.10.1`) · Identity `crs326-core` | RouterOS 7.19.5 · `admin`/`~/.secrets` (`CRS326_CORE_*`) · MikroMCP routerId `crs326-core` (nach MCP-Neustart) · **TP15/TP21.1 ✅ 2026-08-08: PRODUKTIVER L3-Router** (ersetzt ap1) — Routing/DHCP (7 Server + 54 Leases)/DNS (plain `1.1.1.1`, DoH-Cloudflare-TODO)/NAT/WG-Hub (fm+dw live); WAN `vlan100`=Liwest `90.146.33.177` · sw-id 4W04-URHT, MAC 04:F4:1C:B4:17:76 |
|
||||||
| hAP ax² #1 (ap1) | ❌ **Werksreset + getrennt (2026-08-08)** — L3-Rolle → `crs326-core`; Ziel-Mgmt `10.40.99.11` | wird **reiner AP** (WLAN-Neuprovisionierung, eigene Session) · alte L3-Config-Referenz = `routeros/ap1-pre-core-cutover-20260806-073616.rsc` |
|
| hAP ax² #1 (ap1) | **10.40.99.11** (VLAN99) · Identity `ap1` | RouterOS 7.23.2 · `admin`/`~/.secrets` (`ROUTER_AP1_*`) · REST `http://10.40.99.11/rest` · MikroMCP `ap1` (live nach MCP-Neustart) · **TP3-Rebuild ✅ 2026-08-08: reiner Standalone-AP, KEIN CAPsMAN** — Uplink `ether1` (PoE-Port, `poe-out=off`) an crs326-core `ether15`; 6 SSIDs dirtyvader-main/-infra/-iot/-media/-untrusted/-guest, ch36/ch1; jede SSID mit `datapath` (TP3.1-Leck gefixt) · MAC D0:EA:11:4D:40:E0 · Config-Referenz `routeros/.secrets/ap1-prov-base.rsc` |
|
||||||
| hAP ax² #2 (ap2) | 10.40.41.2 (VLAN1, TP19.1-Umzug auf `.99.2` vertagt bis TP21/Nautobot) | MikroMCP (live) · **Port 2 am Omada-Switch** |
|
| hAP ax² #2 (ap2) | **10.40.99.12** (VLAN99) · Identity `ap2` | RouterOS 7.23.2 · `admin`/`~/.secrets` (`ROUTER_AP2_*`) · REST `http://10.40.99.12/rest` · MikroMCP `ap2` · **TP3-Rebuild ✅ 2026-08-08: reiner Standalone-AP** — Uplink `ether1` (PoE) an crs326-branch `ether24`; identisch zu ap1, ch149/ch11 · MAC D0:EA:11:4D:40:3A · Config `routeros/.secrets/ap2-prov-base.rsc` |
|
||||||
|
| **🔑 Einheitliches Admin-PW (2026-08-08)** | alle 4 MikroTik (core/branch/ap1/ap2) | user `admin`, gleiches PW (`~/.secrets/credentials.env` → `ROUTER_*_PASS`/`CRS326_*_PASS`; „Überlebensversicherung", User-Vorgabe). Werks-Defaults ap1/ap2 damit obsolet. |
|
||||||
| NAS (nas-bf, DS923) | 10.40.20.10 | SSH `adm.christoph@10.40.20.10` (kein PW nötig) · Docker (Portainer endpointId=14) |
|
| NAS (nas-bf, DS923) | 10.40.20.10 | SSH `adm.christoph@10.40.20.10` (kein PW nötig) · Docker (Portainer endpointId=14) |
|
||||||
| platform-pg (LXC 105, pve) | 10.40.20.20 | SSH via pve (`pct exec 105 --`) · **TP20 ✅ 2026-07-16: PG16, DB `nautobot`, Creds `postgres/.secrets`** |
|
| platform-pg (LXC 105, pve) | 10.40.20.20 | SSH via pve (`pct exec 105 --`) · **TP20 ✅ 2026-07-16: PG16, DB `nautobot`, Creds `postgres/.secrets`** |
|
||||||
| Nautobot (Portainer-Stack, NAS) | 10.40.20.30 | Portainer ep14, Stack Id 203 · **TP21 ✅ 2026-07-16: `ghcr.io/nautobot/nautobot:3.1.7`, DB=platform-pg, Creds in `docs/nas-vlan20/stacks/210-nautobot.yml` (gitignored)** — Golden-Config-App/Device-Adoption noch offen |
|
| Nautobot (Portainer-Stack, NAS) | 10.40.20.30 | Portainer ep14, Stack Id 203 · **TP21 ✅ 2026-07-16: `ghcr.io/nautobot/nautobot:3.1.7`, DB=platform-pg, Creds in `docs/nas-vlan20/stacks/210-nautobot.yml` (gitignored)** — Golden-Config-App/Device-Adoption noch offen |
|
||||||
|
|||||||
@@ -107,6 +107,7 @@ Kontrolle: MikroMCP (MikroTik) live · ALLNET = Klickanleitungen · Omada → Co
|
|||||||
- **Begründung (kein Widerspruch zu Nautobot, aber ein echter Reibungspunkt):** CAPsMAN erzeugt auf dem CAP **dynamische** Objekte (`wifi13–22`), die nie in dessen eigener Config stehen → Golden-Config-Compliance sieht auf ap2 faktisch nichts. Genau diese Halbheit war die Ursache von TP3.1 (CAPsMAN schrieb `datapath.vlan-id` nicht auf die Leitung und legte die tagged-Member am CAP nicht an).
|
- **Begründung (kein Widerspruch zu Nautobot, aber ein echter Reibungspunkt):** CAPsMAN erzeugt auf dem CAP **dynamische** Objekte (`wifi13–22`), die nie in dessen eigener Config stehen → Golden-Config-Compliance sieht auf ap2 faktisch nichts. Genau diese Halbheit war die Ursache von TP3.1 (CAPsMAN schrieb `datapath.vlan-id` nicht auf die Leitung und legte die tagged-Member am CAP nicht an).
|
||||||
- **Was NICHT verloren geht:** 802.11r/k/v braucht kein CAPsMAN, sondern nur gleiche SSID/PSK/Mobility-Domain → Fast-Roaming bleibt. Preis: Radio-/Kanaländerungen sind zwei Deployments statt eines Provision-Pushes — mit Nautobot ohnehin ein Job-Run.
|
- **Was NICHT verloren geht:** 802.11r/k/v braucht kein CAPsMAN, sondern nur gleiche SSID/PSK/Mobility-Domain → Fast-Roaming bleibt. Preis: Radio-/Kanaländerungen sind zwei Deployments statt eines Provision-Pushes — mit Nautobot ohnehin ein Job-Run.
|
||||||
- **Reihenfolge:** WLAN-Neuprovisionierung erst **nach** dem CRS326-Bring-up (TP15), da Routing/DHCP/DNS vorher umziehen.
|
- **Reihenfolge:** WLAN-Neuprovisionierung erst **nach** dem CRS326-Bring-up (TP15), da Routing/DHCP/DNS vorher umziehen.
|
||||||
|
- ✅ **DURCH (2026-08-08): beide APs als reine Standalone-APs neu provisioniert, CAPsMAN weg.** ap1 `10.40.99.11` (an core `ether15`), ap2 `10.40.99.12` (an branch `ether24`); Uplink geräteseitig auf **`ether1`** (PoE-Port, `poe-out=off`) umgestellt, ether2 aus dem Trunk entfernt. 6 SSIDs (main WPA3+FT / infra/iot/media/untrusted/guest WPA2+3, iot nur 2,4 GHz, media FT=no); **jede SSID mit eigenem `datapath` bridge+vlan-id** → das TP3.1-„cfg-main-2g ohne datapath"-Leck ist strukturell weg. Kanäle non-DFS ap1 ch36/ch1, ap2 ch149/ch11. Client-Test grün (VLAN10/30-Leases korrekt). Configs `routeros/.secrets/ap{1,2}-prov-base.rsc`, PSKs `routeros/.secrets/wlan-psks.env`, As-built → Nautobot via `nautobot/ingest-ap-rebuild.py`. **Einheitliches Admin-PW auf allen 4 MikroTik** (User-Vorgabe). Henne-Ei beim Bring-up: Werks-`192.168.88.1` über Core-Temp-Pfad (`192.168.88.2` + masq, wieder entfernt) via scp+`/import`; Werks-Inline-Overrides auf wifi1/wifi2 mussten geleert werden (`!configuration.ssid !security.*`), sonst funkt die Main-VAP die Werks-SSID. Offen: DoH auf core (nur plain 1.1.1.1), breiterer CLAUDE.md-Gotcha-Cleanup (ap1-als-L3-Router-Referenzen sind überholt).
|
||||||
|
|
||||||
### TP3.1 — ap2 VLAN-Trunk-Fix ▸ ✅ Config live (2026-07-01) — `routeros/tp3.1-ap2-vlan.rsc`
|
### TP3.1 — ap2 VLAN-Trunk-Fix ▸ ✅ Config live (2026-07-01) — `routeros/tp3.1-ap2-vlan.rsc`
|
||||||
- **Root Cause:** ap2 (CAP, local forwarding) hatte `bridge vlan-filtering=no` → `datapath.vlan-id` wurde nie
|
- **Root Cause:** ap2 (CAP, local forwarding) hatte `bridge vlan-filtering=no` → `datapath.vlan-id` wurde nie
|
||||||
|
|||||||
212
nautobot/ingest-ap-rebuild.py
Normal file
212
nautobot/ingest-ap-rebuild.py
Normal file
@@ -0,0 +1,212 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""BF-Netzwerk -> Nautobot: ap1/ap2 As-built als REINE Access Points (TP3-Rebuild, 2026-08-08).
|
||||||
|
|
||||||
|
Governance-Auflage "intended config zurueckschreiben" nach dem WLAN-Neuaufbau (CAPsMAN abgeschafft,
|
||||||
|
L3 auf crs326-core). Re-modelliert ap1 + ap2 identisch als Standalone-APs:
|
||||||
|
|
||||||
|
- Rolle Router -> Wireless AP, Status Active
|
||||||
|
- Mgmt vlan99: ap1 10.40.99.11, ap2 10.40.99.12 (primary_ip4)
|
||||||
|
- Uplink = ether1 (PoE-Port), Trunk: native VLAN1 + tagged 10/20/30/40/50/90/99
|
||||||
|
- WLAN-VAPs (wifi1/wifi2 master + Slaves) je SSID->VLAN
|
||||||
|
- ENTFERNT die stale L3-Objekte von ap1: vlan10/20/30/40/50/90/100 (SVIs), wg-hub
|
||||||
|
|
||||||
|
Muster wie ingest-crs326-ports.py: get-or-create + Delta-PATCH, idempotent.
|
||||||
|
Verifikation NUR per GraphQL (tagged_vlans liest sich per REST als null).
|
||||||
|
PSKs werden bewusst NICHT modelliert (liegen lokal am Geraet).
|
||||||
|
|
||||||
|
export NAUTOBOT_TOKEN=...
|
||||||
|
python3 nautobot/ingest-ap-rebuild.py # Dry-Run
|
||||||
|
python3 nautobot/ingest-ap-rebuild.py --apply # schreiben
|
||||||
|
"""
|
||||||
|
import json, os, sys, urllib.request, urllib.error, urllib.parse
|
||||||
|
|
||||||
|
BASE = "http://10.40.20.30:8080/api"
|
||||||
|
TOKEN = os.environ.get("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 payload={json.dumps(data)}\n -> {e.code} {e.read().decode()[:500]}")
|
||||||
|
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 str(match)
|
||||||
|
obj = find(path, **match)
|
||||||
|
if obj:
|
||||||
|
log.append(f" = {name}")
|
||||||
|
return obj
|
||||||
|
if DRY:
|
||||||
|
log.append(f" + {name} (dry-run)")
|
||||||
|
return {"id": f"DRY-{name}"}
|
||||||
|
obj = req("POST", path + "/", payload)
|
||||||
|
log.append(f" + {name}")
|
||||||
|
return obj
|
||||||
|
|
||||||
|
|
||||||
|
def patch(path, obj, payload, label):
|
||||||
|
delta = {}
|
||||||
|
for k, v in payload.items():
|
||||||
|
cur = obj.get(k)
|
||||||
|
cur_id = cur.get("id") if isinstance(cur, dict) else cur
|
||||||
|
if cur_id != v:
|
||||||
|
delta[k] = v
|
||||||
|
if not delta:
|
||||||
|
log.append(f" = {label} (unveraendert)")
|
||||||
|
return obj
|
||||||
|
if DRY:
|
||||||
|
log.append(f" ~ {label}: {list(delta)} (dry-run)")
|
||||||
|
return obj
|
||||||
|
out = req("PATCH", f"{path}/{obj['id']}/", delta)
|
||||||
|
log.append(f" ~ {label}: {list(delta)}")
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
def sid(name):
|
||||||
|
return find("/extras/statuses", name=name)["id"]
|
||||||
|
|
||||||
|
|
||||||
|
def rid(name):
|
||||||
|
return find("/extras/roles", name=name)["id"]
|
||||||
|
|
||||||
|
|
||||||
|
ETH, VIRT, BR, WIFI = "1000base-t", "virtual", "bridge", "ieee802.11ax"
|
||||||
|
S_ACTIVE = sid("Active")
|
||||||
|
R_AP = rid("Wireless AP")
|
||||||
|
|
||||||
|
vg = find("/ipam/vlan-groups", name="BF")
|
||||||
|
V = {vid: find("/ipam/vlans", vid=vid, vlan_group=vg["id"]) for vid in (1, 10, 20, 30, 40, 50, 90, 99)}
|
||||||
|
ns_global = find("/ipam/namespaces", name="Global")["id"]
|
||||||
|
|
||||||
|
DEVS = {n: find("/dcim/devices", name=n) for n in ("ap1", "ap2")}
|
||||||
|
TRUNK = [10, 20, 30, 40, 50, 90, 99]
|
||||||
|
STALE = ["vlan10", "vlan20", "vlan30", "vlan40", "vlan50", "vlan90", "vlan100", "wg-hub"]
|
||||||
|
|
||||||
|
# VAP -> (Band-Beschreibung, VLAN) — Master fuehren dirtyvader-main (VLAN10)
|
||||||
|
VAPS = {
|
||||||
|
"wifi1": ("5GHz master - dirtyvader-main", 10),
|
||||||
|
"wifi2": ("2.4GHz master - dirtyvader-main", 10),
|
||||||
|
"w5-infra": ("5G slave - dirtyvader-infra", 20),
|
||||||
|
"w5-media": ("5G slave - dirtyvader-media", 40),
|
||||||
|
"w5-untrusted": ("5G slave - dirtyvader-untrusted", 50),
|
||||||
|
"w5-guest": ("5G slave - dirtyvader-guest", 90),
|
||||||
|
"w2-infra": ("2.4G slave - dirtyvader-infra", 20),
|
||||||
|
"w2-iot": ("2.4G slave - dirtyvader-iot (nur 2.4GHz)", 30),
|
||||||
|
"w2-media": ("2.4G slave - dirtyvader-media", 40),
|
||||||
|
"w2-untrusted": ("2.4G slave - dirtyvader-untrusted", 50),
|
||||||
|
"w2-guest": ("2.4G slave - dirtyvader-guest", 90),
|
||||||
|
}
|
||||||
|
MGMT = {"ap1": "10.40.99.11/24", "ap2": "10.40.99.12/24"}
|
||||||
|
|
||||||
|
|
||||||
|
def ifc(dev, name, typ, desc, mode=None, untagged=None, tagged=None):
|
||||||
|
obj = goc("/dcim/interfaces", {"device": dev, "name": name},
|
||||||
|
{"device": DEVS[dev]["id"], "name": name, "type": typ,
|
||||||
|
"status": S_ACTIVE, "description": desc}, label=f"{dev}:{name}")
|
||||||
|
if DRY or str(obj["id"]).startswith("DRY"):
|
||||||
|
return obj
|
||||||
|
# Beim Zurueckstufen (kein tagged-Mode) zuerst tagged_vlans leeren, sonst 400 beim mode-Clear
|
||||||
|
if not tagged:
|
||||||
|
req("PATCH", f"/dcim/interfaces/{obj['id']}/", {"tagged_vlans": []})
|
||||||
|
p = {"description": desc, "type": typ, "mode": mode or "",
|
||||||
|
"untagged_vlan": V[untagged]["id"] if untagged is not None else None}
|
||||||
|
obj = patch("/dcim/interfaces", obj, p, f"{dev}:{name}")
|
||||||
|
if tagged:
|
||||||
|
req("PATCH", f"/dcim/interfaces/{obj['id']}/", {"tagged_vlans": [V[t]["id"] for t in tagged]})
|
||||||
|
return obj
|
||||||
|
|
||||||
|
|
||||||
|
def remove_iface(dev, name):
|
||||||
|
o = find("/dcim/interfaces", device=dev, name=name)
|
||||||
|
if not o:
|
||||||
|
log.append(f" . {dev}:{name} (schon weg)")
|
||||||
|
return
|
||||||
|
if DRY:
|
||||||
|
log.append(f" - {dev}:{name} (dry-run)")
|
||||||
|
return
|
||||||
|
req("DELETE", f"/dcim/interfaces/{o['id']}/")
|
||||||
|
log.append(f" - {dev}:{name}")
|
||||||
|
|
||||||
|
|
||||||
|
def assign_mgmt(dev_obj, addr, desc):
|
||||||
|
"""IP get-or-create, exklusiv an dev:vlan99 haengen (fremde Assignments loesen), primary setzen."""
|
||||||
|
ip = find("/ipam/ip-addresses", address=addr) or goc(
|
||||||
|
"/ipam/ip-addresses", {"address": addr},
|
||||||
|
{"address": addr, "status": S_ACTIVE, "namespace": ns_global, "description": desc}, label=addr)
|
||||||
|
if DRY or str(ip["id"]).startswith("DRY"):
|
||||||
|
return
|
||||||
|
intf = find("/dcim/interfaces", device=dev_obj["name"], name="vlan99")
|
||||||
|
assigns = req("GET", f"/ipam/ip-address-to-interface/?ip_address={ip['id']}&limit=50").get("results", [])
|
||||||
|
have = False
|
||||||
|
for a in assigns:
|
||||||
|
if a["interface"]["id"] == intf["id"]:
|
||||||
|
have = True
|
||||||
|
else:
|
||||||
|
req("DELETE", f"/ipam/ip-address-to-interface/{a['id']}/")
|
||||||
|
log.append(f" - {addr} losgeloest von {a['interface'].get('display', '?')}")
|
||||||
|
if not have:
|
||||||
|
req("POST", "/ipam/ip-address-to-interface/", {"ip_address": ip["id"], "interface": intf["id"]})
|
||||||
|
log.append(f" + {addr} -> {dev_obj['name']}:vlan99")
|
||||||
|
req("PATCH", f"/dcim/devices/{dev_obj['id']}/", {"primary_ip4": ip["id"]})
|
||||||
|
|
||||||
|
|
||||||
|
def set_mgmt(dev, addr):
|
||||||
|
assign_mgmt(DEVS[dev], addr, f"{dev} Mgmt (VLAN99)")
|
||||||
|
|
||||||
|
|
||||||
|
# --- Core-Mgmt-Konsistenz: crs326-core primary = .99.1 (das stale .99.11 wandert unten zu ap1) ---
|
||||||
|
core = find("/dcim/devices", name="crs326-core")
|
||||||
|
if core:
|
||||||
|
print("== crs326-core: Mgmt-IP korrigieren -> 10.40.99.1")
|
||||||
|
assign_mgmt(core, "10.40.99.1/24", "crs326-core Mgmt/GW SVI (VLAN99)")
|
||||||
|
|
||||||
|
|
||||||
|
COMMENT = ("TP3-Rebuild 2026-08-08: reiner Standalone-AP (kein CAPsMAN). L3 auf crs326-core. "
|
||||||
|
"Uplink ether1 (PoE, poe-out=off), Trunk native VLAN1 + tagged 10/20/30/40/50/90/99. "
|
||||||
|
"6 SSIDs dirtyvader-main/-infra/-iot/-media/-untrusted/-guest. PSKs lokal am Geraet.")
|
||||||
|
|
||||||
|
for dev in ("ap1", "ap2"):
|
||||||
|
if not DEVS[dev]:
|
||||||
|
print(f"!! Device {dev} nicht in Nautobot gefunden - uebersprungen")
|
||||||
|
continue
|
||||||
|
print(f"== {dev}: -> reiner Access Point")
|
||||||
|
patch("/dcim/devices", DEVS[dev], {"role": R_AP, "status": S_ACTIVE}, f"{dev} role/status")
|
||||||
|
ifc(dev, "bridge", BR, "vlan-filtering=yes; VLAN1 untagged, 10-99 tagged")
|
||||||
|
ifc(dev, "ether1", ETH, "Uplink-Trunk (PoE-Port, poe-out=off): native VLAN1 + tagged 10/20/30/40/50/90/99",
|
||||||
|
mode="tagged", untagged=1, tagged=TRUNK)
|
||||||
|
ifc(dev, "ether2", ETH, "frei (Alt-Uplink, seit ether1-Umstellung ungenutzt)")
|
||||||
|
for n in (3, 4, 5):
|
||||||
|
ifc(dev, f"ether{n}", ETH, "frei")
|
||||||
|
ifc(dev, "vlan99", VIRT, "Mgmt SVI (VLAN99)", mode="tagged", tagged=[99])
|
||||||
|
if not DRY:
|
||||||
|
v99 = find("/dcim/interfaces", device=dev, name="vlan99")
|
||||||
|
br = find("/dcim/interfaces", device=dev, name="bridge")
|
||||||
|
req("PATCH", f"/dcim/interfaces/{v99['id']}/", {"parent_interface": br["id"]})
|
||||||
|
for name, (desc, vlan) in VAPS.items():
|
||||||
|
ifc(dev, name, WIFI, desc, mode="access", untagged=vlan)
|
||||||
|
for name in STALE:
|
||||||
|
remove_iface(dev, name)
|
||||||
|
set_mgmt(dev, MGMT[dev])
|
||||||
|
if not DRY:
|
||||||
|
req("PATCH", f"/dcim/devices/{DEVS[dev]['id']}/", {"comments": COMMENT})
|
||||||
|
|
||||||
|
print("\n".join(log))
|
||||||
|
print(f"\n{'DRY-RUN - nichts geschrieben. Mit --apply ausfuehren.' if DRY else 'ANGEWENDET.'}")
|
||||||
Reference in New Issue
Block a user