TP15: CRS326-Core als L3-Router aktiviert

- crs326-core nimmt Routing/DHCP/DNS/NAT/WAN/WireGuard von ap1 ueber.
- L2-Portplan korrigiert (ap1 #15, Arris WAN #16, branch #8, Sonos #2/3/5/6,
  IoT #14, VLAN10-Access #11, VLAN1-Hybrid #3/7/9/10/12/17-24).
- Backup-RSC gefixt (ether3-24 disabled) und via fixed-RSC eingespielt.
- Gestagede L3-Config enabled in einem Rutsch.
- Falsche statische Default-Route 0.0.0.0/0 -> 10.40.41.1 entfernt,
  WAN-DHCP via 90.146.33.177/24 aktiv.
- SFP-Ports bleiben disabled (Stromersparnis).
- ap1 ist L3-stillgelegt und wird in naechster Session reiner AP.

Backup-Dateien (*.backup) und leases-export.json bleiben lokal.

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2026-08-07 22:39:29 +02:00
parent d8c8c3b828
commit d281a0f36f
25 changed files with 3214 additions and 9 deletions

2
.gitignore vendored
View File

@@ -11,3 +11,5 @@ docs/nas-vlan20/inspect_*.json
# RouterOS-Config-Exports/Backups enthalten Live-Secrets (WiFi-Passphrasen etc.) → nicht committen
routeros/state-backups/
routeros/ap1-wg-hub.privkey
routeros/tp15-core-cutover-intended.rsc

81
PLAN.md Normal file
View File

@@ -0,0 +1,81 @@
# TP15/TP21 Kritischer Pfad — Plan (Stand 2026-08-05, aktualisiert)
> **User-Entscheidung:** Cutover morgen (2026-08-06). Heuteiger Fokus = **Nautobot-Datenmodell für `crs326-core` ausbauen**, damit der Cutover über Nautobot Golden Config geplant/gesteuert werden kann. Governance-Ausnahme #3 damit obsolet für den Config-Teil; die physischen Cutover-Schritte bleiben ein geplantes Wartungsfenster.
## Aktueller Stand (Read-only Checks)
| Komponente | Befund |
|---|---|
| **ap1** | Noch aktiver L3-Router, 53 DHCP-Reservierungen, Public-IP `90.146.35.38`, WG-Hub live. RouterOS 7.23.2. |
| **crs326-core** | Erreichbar nur über VLAN1-Temp `10.40.41.211`. **Keine L3-Config** auf dem Gerät. `10.40.99.11` tot, weil sw1 #12 kein VLAN99 tagged trägt. |
| **crs326-branch** | Produktiv auf `10.40.99.13`, läuft. |
| **Nautobot** | App live (`10.40.20.30:8080`), Golden-Config-App installiert, Git-Repo zu Gitea verbunden. **Aber:** `crs326-core` nur rudimentär modelliert (7 Interfaces, keine VLAN-SVIs, keine SecretsGroup), GC-Templates/Regeln nur PoC-Niveau, Scheduler+Worker unhealthy. |
| **MikroMCP** | In dieser Session **nicht** für core/branch geladen — Env-Variablen-Mismatch (`~/.secrets` exportiert `CRS326_*`, `routers.yaml` erwartet `ROUTER_CRS326_*`). |
| **Portainer ep14** | API-Token stale/ungültig → Docker-Management auf NAS gestört. |
| **Postgres / pve-mcp / Gitea / CF-Token** | Grün. |
## Heutiges Ziel (2026-08-05)
**Nautobot-Datenmodell für `crs326-core` so weit vervollständigen, dass morgen ein Config-Render für den L3-Router möglich ist.**
Konkrete Arbeitspakete:
1. MikroMCP reparieren & neu starten (core/branch schreibbar für Tests).
2. Portainer-Token rotieren (damit Nautobot-Container-Logs/Health prüfbar sind).
3. Nautobot worker/scheduler healen.
4. `crs326-core` Device-Modell in Nautobot vervollständigen:
- Interfaces `ether6`–`ether24`, `sfp-sfpplus1`, `sfp-sfpplus2`.
- VLAN-SVIs (`vlan10`–`vlan100`) + IP-Adressen (GW `.1` je VLAN).
- Access/Trunk-Ports mit korrekten untagged/tagged VLANs gemäß Ziel-Layout.
- `SecretsGroup` mit RouterOS-Credentials.
- `Platform` = RouterOS.
- Status = `Planned` (morgen nach Cutover `Active`).
5. DHCP-Scopes, statische DNS-Einträge, Firewall-Zonen/-Regeln, NAT, WireGuard-Peers, CAKE-Parameter im Datenmodell abbilden (Custom Fields oder native Modelle, je nach Nautobot-Möglichkeit).
6. Golden-Config SoT-Aggregation Query erweitern.
7. RouterOS-Jinja-Template für L3-Core vervollständigen (ggf. anhand von `routeros/tp15-core-l3-staged.rsc`).
8. GC Compliance-Features/Regeln für kritische Bereiche definieren.
9. Backup/Intended-Job auf `crs326-branch` testen (Proof-of-Concept für MikroTik-Switch).
10. ap1-Reservierungen + WG-Privkey maschinell exportieren (Input für Datenmodell + Template).
## Morgiges Ziel (2026-08-06) — Cutover
Voraussetzungen vor dem Fenster:
- `crs326-core` in Nautobot vollständig modelliert.
- Golden Config kann ein `intended`-Config für core rendern.
- MikroMCP schreibbar, dryRun auf core erfolgreich.
- ap1-Voll-Backup frisch gezogen.
- Port-Umzugsplan final: core #4=ap1, #8=branch, #16=Arris/WAN; sw1-Rest stilllegen.
- iPhone-Hotspot als Rettungspfad parat.
- Wartungsfenster mit Hausmitteilung (ca. 60 Min).
Cutover-Ablauf:
1. Nautobot `intended` Config für core rendern/exportieren.
2. ap1 als Router stilllegen (DHCP/DNS/NAT/GW-IPs/WAN-Client/WG-Hub disable).
3. ap1-Uplink `ether2` → `ether1` (PoE), `poe-out=off`.
4. Kabel von sw1 auf core umstecken.
5. core-L3-Config aktivieren (Nautobot-Deploy oder REST/MikroMCP-Apply).
6. Abnahme je VLAN, branch, WG-Handshake, CF/WARP, Public-IP.
7. Rollback-Test (optional, aber empfohlen).
## Nach dem Cutover
- Mgmt-Renumber (branch `.99.13` → `.99.12`, core `.99.11` konsolidieren).
- VLAN1-Temp-IPs entfernen.
- Nautobot: core `Planned` → `Active`.
- Doku aktualisieren (`teilprojekte.md`, `CLAUDE.md`, `ip-plan.md`).
- WLAN-Neuprovisionierung (CAPsMAN-Abbau) als eigene Session.
- TP5/TP6 danach.
## Rollback (während Cutover)
1. core-L3 deaktivieren / Config zurückziehen.
2. Kabel zurück auf sw1.
3. ap1 DHCP/DNS/NAT/GW-IPs/WAN-Client/WG-Hub wieder aktivieren.
4. Netz wieder im heutigen Zustand.
## Erste Schritte jetzt
1. Task-Liste anlegen.
2. MikroMCP envPrefix/Secrets fixen + Neustart.
3. Portainer-Token rotieren.
4. Nautobot worker/scheduler healen.
5. Dann in das `crs326-core`-Datenmodell einsteigen.

View File

@@ -344,7 +344,7 @@ FT kaschierte das bisher als „Roaming" statt „schwache Abdeckung".
### TP14.5 — Session E: Freigabe-Gate → TP5-Firewall ▸ 🔜 (ZULETZT)
- Erst wenn TP14.1–14.3 durch (alle Origins/Connectoren in VLAN20) → dann TP5 (Deny-by-default) starten. Reines Reihenfolge-Gate, kein eigener Change.
### TP15 — HW-Refresh: Switching + Router + DC-USV ▸ 🟢 **CRS326-Bring-up + Branch-Tausch DURCH (2026-07-23)**; Core-Tausch/L3-Umzug offen (eigene Session) — `docs/hw-refresh-requirements.md`, Memory `project_mikrotik_commit_nautobot_gc`
### TP15 — HW-Refresh: Switching + Router + DC-USV ▸ 🟢 **CRS326-Bring-up + Branch-Tausch DURCH (2026-07-23)**; Core-Tausch/L3-Umzug **vorbereitet, Cutover-Config materialisiert (2026-08-05)**, Cutover = nächstes Wartungsfenster — `docs/hw-refresh-requirements.md`, Memory `project_mikrotik_commit_nautobot_gc`
- ✅ **BRING-UP BEIDER CRS326 + BRANCH-TAUSCH gegen sw2 — DURCH (2026-07-23, Claude via REST unter Governance-Ausnahme #2).** Ablauf/As-built:
- **Zugriffsweg (Henne-Ei gelöst):** Erst-Reihenfolge war **core zuerst** (User-Korrektur — core hängt an sw1 und ist der L2-Pfad, über den branch überhaupt erreichbar wird; branch war isoliert). Zugriff auf die Werks-`192.168.88.1` über **temporäre ap1-Zweit-IP `192.168.88.2/24` + srcnat-Masquerade `192.168.88.0/24`** (beide `TP15-temp`, nach Bring-up wieder entfernt — Rückweg nötig, weil die Werks-Box keine Route zu VLAN10 hat). Config dann komplett per **REST** (`/system/script` PUT+run), kein Winbox-Paste. **Winbox-MAC-Connect scheiterte reproduzierbar** („macconnection sync timeout" über WLAN/quer durch den Fabric — braucht selben Draht).
- **Identitäten am Gerät verifiziert (nicht nur Doku):** `core` = sw-id **4W04-URHT**, MAC `04:F4:1C:B4:17:76`, Serial `HKA0AREXJFM`, PW `X9…`; `branch` = sw-id **RGZJ-UK0T**, MAC `04:F4:1C:B4:15:58`, Serial `HKA0AJJA52F`, PW `PHR…` (ap2 hängt an branch **ether24** — bridge-host bestätigt = definitiv branch). **PWs in `~/.secrets/credentials.env`.**
@@ -354,6 +354,8 @@ FT kaschierte das bisher als „Roaming" statt „schwache Abdeckung".
- 🐞 **Gotchas festgehalten:** (1) **Kabel ether3↔ether5 waren beim Umstecken vertauscht** → ein AZ-Sonos landete als Access-VLAN10 in `10.40.10.190` **und** blutete via SonosNet nach VLAN40 (= Dual-Homing-Muster, `project_dual_homing_slowness_incident`) → Kabeltausch gefixt, beide AZ-Sonos jetzt sauber VLAN40 (`.191`/`.192` bound). (2) RouterOS-Script-`policy`-Feld per REST wählerisch → **weglassen** (admin-Default = volle Policy). (3) Disable-Regex mit `^…$`-Ankern griff nicht → `[find name!="ether1" and …]` nutzen. (4) REST-`PUT`-Antwort mit mehrzeiligem `source` ist kein sauberes JSON → ID separat via `?.proplist=.id` holen. (5) Claude-Code-Auto-Mode-Classifier blockt Device-Config-*Writes* per Bash (Reads ok) → `autoMode.allow`-Regel in `.claude/settings.local.json` nötig (kann Claude sich nicht selbst setzen).
- **🔜 Als Nächstes (eigene Session, Übergabe): Core-Tausch = L3-Umzug** (Routing/DHCP/DNS/CF-Mesh von ap1 → crs326-core) — **Vollausfall-Fenster**, s. `docs/tp15-core-swap-session-handoff-prompt.md`. Danach WLAN-Neuprovisionierung (TP3, CAPsMAN-Abbau). Finaler Mgmt-Renumber dann: branch `.99.13`→`.99.12`, core bleibt `.99.11`.
- ✅ **Core-Swap VORBEREITET (2026-07-23, reine Prep-Session, kein Netz-Eingriff):** ap1s Live-L3-Config vollständig gelesen (MikroMCP) und als **staged Config** materialisiert → `routeros/tp15-core-l3-staged.rsc` (alles Betriebsumlenkende `disabled=yes`, im Cutover in einem Rutsch `enable`; ~50 Reservierungen + WG-Privkey bewusst erst im Fenster maschinell von ap1 geholt statt abgetippt). Runbook/Rollback/Verifikation im Plan. **3 Fenster-Start-Entscheide festgelegt:** (1) **WG-Hub (TP10 FM/DW-VPN) migriert MIT** — war im Handoff-Prompt übersehen, hängt an ap1s Public-Edge und kann dort nicht bleiben; (2) Governance = **frische Direkt-Ausnahme #3** (REST/MikroMCP), danach intended→Nautobot; (3) Rettungspfad = **iPhone-Hotspot**.
- ✅ **Nautobot-Modell + Golden-Config-Template für L3-Cutover vervollständigt (2026-08-05):** `crs326-core` jetzt Rolle *Router*, alle VLAN-SVIs (`vlan10/20/30/40/50/90/99/100`) + Gateway-IPs (`.1`) modelliert; `nautobot/mikrotik_routeros.j2` (Gitea `bf-golden-config`) erzeugt für `crs326-core` die vollständige staged L3-Konfig (Interface-Lists, GW-IPs, WAN-DHCP, DHCP-Server/Pools, DNS, NAT, Firewall, WG-Hub) und für alle anderen MikroTik-Geräte nur die Identität. Git-Sync + Generate Intended Config erfolgreich getestet; intended config für `crs326-core` liegt in Nautobot vor.
- ✅ **ap1-Cutover-Daten exportiert + in Cutover-Config eingebaut (2026-08-05):** 54 statische DHCP-Leases aus ap1 (`dhcp-v10`/`v20`/`v30`/`v40`) nach `routeros/ap1-leases-to-core.rsc` überführt; WG-Hub-Privkey von ap1 geholt und in die lokale Cutover-Config `routeros/tp15-core-cutover-intended.rsc` eingesetzt (der **WG-Privkey landet NICHT im Git-Template** — Secret).
- 🐞 **Prep-Gotchas:** (a) **MikroMCP kann core noch nicht schreiben** — `ROUTER_CRS326_CORE_*` im MCP-Prozess nicht geladen (jeder Zugriff „Missing environment variable") → vor dem Fenster MCP neu starten oder REST-curl gegen `http://10.40.41.211/rest`. (b) Mgmt-`.99.11` tot (sw1 #12 trägt kein 99T), nur VLAN1-Temp `.41.211` erreichbar. (c) **WG-Hub:** Privkey von ap1 mitnehmen (sonst FM/DW-Reconfig nötig). (d) core-vlan99 wird GW `.1`; Bring-up-`.11` dadurch redundant (Fenster-Reconcile). (e) VLAN1 raus: Hybrid-VLAN1-Drops sw1 GE3/7/10 beim Umstecken einem echten VLAN zuordnen oder stilllegen.
- ✅ **WG-Endpoint-Frage geklärt (2026-07-23):** Spokes adressieren den Hub über `bf4.emsenhuber.net:51821` = CF-A-Record (proxied=false, TTL 120, aktuell `90.146.35.38`). **Kein Updater auf ap1** → nichts zu migrieren; `bf4` folgt der IP manuell (offener TP10-Follow-up). Zieht core am WAN eine neue IP (Lease sticky, eher nicht), via `CF_API_TOKEN` nachziehen: `bf4`-Record + Access-Policy `bf4-home-ip` (`90.146.35.38/32`). **Chance:** core ist nicht `device-mode=home` → scheduler/fetch gehen → `bf4`-DDNS-Auto-Updater dort bauen (schließt TP10-Follow-up).
- 📋 **Session-Handover** für die Cutover-Session: `docs/tp15-core-swap-session-handover.md` (Einstieg + Pre-Window-Checkliste + Kurz-Runbook; Original-Auftrag bleibt `docs/tp15-core-swap-session-handoff-prompt.md`).
@@ -523,7 +525,7 @@ FT kaschierte das bisher als „Roaming" statt „schwache Abdeckung".
- **Verwandt/Abgrenzung:** vw ist auch in **TP16** offen (CF-Access: `bypass`→WARP-geschützt) — **DB-HA ≠ Access**, getrennt behandeln. Reziprokes 2-Site-Muster analog **TP16**/**TP18**.
- Abh.: **TP20** (Tier-1 Postgres muss stehen), **TP10** (WG-Transport für Phase-2), **TP18** (Backup-Schirm). Kein Blocker für TP21.
### TP21 — Nautobot Golden Config (config-as-code Control-Plane) ▸ ⏳ **Plattform + Golden-Config-App live** (App-Install nachträglich verifiziert 2026-07-22, Doku war stale), **Datenmodell/Git-Backend/Device-Adoption noch offen** — Memory `project_mikrotik_commit_nautobot_gc`
### TP21 — Nautobot Golden Config (config-as-code Control-Plane) ▸ 🟢 **Plattform + Golden-Config-App live** (App-Install nachträglich verifiziert 2026-07-22), **Basisdaten + Git-Backend + L3-Core-Template vervollständigt (2026-08-05)**; Device-Adoption/Compliance-Push noch offen — Memory `project_mikrotik_commit_nautobot_gc`
- **Ziel:** modellierter Control-Plane über den MikroTik-**RouterOS**-Kern: GUI-SoT + **Config-Compliance/Drift** + intended-config + Push. **Ergänzt** MikroMCP (interaktiv/Debug), **ersetzt es nicht** (deklarativ/DR/Compliance). Aus User-Recherche 2026-07-14: MikroTik in Golden Config **erstklassig** (`mikrotik_routeros` + `_api`, Nornir/NAPALM, backup+deploy).
- **Scope-Grenze:** RouterOS in-model = **Router (ap1/ap2, dauerhaft — kein RB5009) + beide CRS326-Switches** (RouterOS-Boot) → Golden Config deckt den **ganzen Kabel-Fabric**. Außerhalb bleibt nur **Omada-WLAN** (eigener Controller, spielt aber laut TP15-Korrektur ohnehin keine Rolle mehr).
- 🔒 **Wird zum alleinigen Config-Weg für Netzgeräte (Governance-Entscheid 2026-07-16, s. oben):** ab hier keine ad-hoc MikroMCP-Writes/Switch-UI-Klicks mehr auf Dauer — Nautobot managed den Fleet.
@@ -539,8 +541,11 @@ FT kaschierte das bisher als „Roaming" statt „schwache Abdeckung".
- **ap1-Interfaces (26):** ether1-5, bridge, lo, wg-hub, vlan10-100, wifi1/wifi2, 7 VAPs — inkl. MACs, `ether2` als Trunk (untagged VLAN1 + tagged 10-100), VAPs als Access-Ports auf ihrem VLAN. 9 Gateway-IPs an ihren Interfaces. Platzhalter-Interface `mgmt` auf ap1 gelöscht (existiert real nicht).
- **WLAN:** logischer Controller `bf-wlan` (**kein** CAPsMAN — `controller_device` bewusst leer, die Control-Plane *ist* Nautobot GC) + Gruppe `BF-WLAN-APs` (ap1+ap2) + **6 WirelessNetworks mit VLAN-Bindung** + 2 RadioProfiles (2,4 GHz ch1 / 5 GHz ch36, Domain AT).
- 🐞 **API-Gotcha:** `tagged_vlans` wird vom REST-Serializer in Detail-/List-Views als `null` zurückgegeben, **obwohl korrekt gespeichert** — Verifikation nur über **GraphQL** verlässlich. Kostet sonst eine Stunde Fehlersuche an einem Nicht-Problem. Ebenso: Relationen kommen per Default ohne `display` (`?depth=1` nötig), und `VLANGroup`/`Prefix`/`VLAN` brauchen passende `content_types` am LocationType, sonst 400.
- ⚠️ **Offen/nachzutragen:** exaktes Modell von sw1 + cudy1 (aktuell `… (Modell TBD)`); Standort von sw1/sw2/cudy1 (hängen an `BF` statt an einem Bereich); ap2s `primary_ip4` zeigt noch auf das tote `10.40.41.2`; CRS326-Ports bewusst noch nicht angelegt (erst wenn die Portbelegung steht).
- **Offen:** Git-Backend-Anbindung an Gitea; ap1/ap2-Adoption via RouterOS-API; ob OpenWrt je in-model muss (custom Nornir-Dispatcher — nur relevant falls der Router-Pfad je auf OpenWrt+cake-autorate wechselt, aktuell nicht geplant).
- ✅ **CRS326-Core L3-Device-Modell + Ports angelegt (2026-08-05):** `crs326-core` jetzt Rolle *Router*, alle VLAN-SVIs (`vlan10/20/30/40/50/90/99/100`) + Gateway-IPs `.1` (außer vlan100) in Nautobot; Portbelegung via `nautobot/ingest-crs326-ports.py` aktualisiert. `crs326-branch` bleibt *Switch* mit Mgmt-IP `.99.13` (Zielrenumber `.12` im Cutover-Fenster).
- ⚠️ **Offen/nachzutragen:** exaktes Modell von sw1 + cudy1 (aktuell `… (Modell TBD)`); Standort von sw1/sw2/cudy1 (hängen an `BF` statt an einem Bereich); ap2s `primary_ip4` zeigt noch auf das tote `10.40.41.2` (Ziel `.99.2`, Cutover-Fenster).
- ✅ **Git-Backend an Gitea angebunden (2026-08-05):** Repository `bf-golden-config` (`jinja_templates/mikrotik_routeros.j2`) sync-t in Nautobot; Generate Intended Config für `crs326-core` erfolgreich, intended config in GC-Tabelle vorhanden.
- ✅ **Lokale Cutover-Config materialisiert:** `routeros/tp15-core-cutover-intended.rsc` enthält die aus Nautobot gerenderte Config plus die 54 ap1-Leases und den WG-Hub-Privkey (lokales Fenster-Artefakt, **nicht** im Golden-Config-Repo).
- **Offen:** ap1/ap2/crs326-Adoption + Compliance-Push (RouterOS-API); RouterOS-Nornir-Dispatcher falls nötig; Branch-Renumber `.13→.12` und AP-Mgmt-IP-Fix in Datenmodell beim Cutover; Secret-Handling für WG-Privkey in Nautobot/Nornir klären (aktuell lokale Injektion).
- Abh.: **TP20** (Postgres muss stehen), Gitea ✅, RouterOS-Router (ap1/ap2, dauerhaft).
### TP-R — Router-Capabilities-Sweep ▸ FERTIG (`docs/research/routeros-capabilities.md`)

View File

@@ -31,17 +31,17 @@ routers:
# --- TP15 CRS326-Bring-up (2026-07-23) ---
# Mgmt auf VLAN99. www=plain HTTP:80 (wie ap1/ap2) -> tls.enabled:false.
# ENV in ~/.claude.json (mikromcp-Block): ROUTER_CRS326_CORE_USER/PASS, ROUTER_CRS326_BRANCH_USER/PASS.
# Wird erst nach MCP-Neustart (neue Session) live; diese Session Verify via direktem REST-curl + ap1-ping.
# ENV via ~/.secrets/exports.zsh: CRS326_CORE_USER/PASS, CRS326_BRANCH_USER/PASS.
# Wird erst nach MCP-Neustart (neue Session) live; diese Session Verify via direktem REST-curl.
crs326-core:
host: "10.40.99.11"
host: "10.40.41.211" # TEMP cutover mgmt IP until VLAN99 trunk through sw1 is live
port: 80
tls:
enabled: false
rejectUnauthorized: true
credentials:
source: "env"
envPrefix: "ROUTER_CRS326_CORE"
envPrefix: "CRS326_CORE"
tags: ["bf", "switch", "core", "future-l3-router"]
rosVersion: "7.19.5"
@@ -53,6 +53,6 @@ routers:
rejectUnauthorized: true
credentials:
source: "env"
envPrefix: "ROUTER_CRS326_BRANCH"
envPrefix: "CRS326_BRANCH"
tags: ["bf", "switch", "branch"]
rosVersion: "7.19.5"

View File

@@ -0,0 +1,153 @@
#!/usr/bin/env python3
"""BF-Netzwerk -> Nautobot: crs326-core L3-Datenmodell ergaenzen (TP15 Cutover-Prep).
Erstellt fuer crs326-core:
- vlan10..vlan90, vlan100 als virtuelle Bridge-Subinterfaces (tagged)
- GW-IPs 10.40.<vlan>.1/24 (v10..v90)
- Primaer-IP bleibt 10.40.99.11/24 (Bring-up-Mgmt)
- Rolle Router, Status bleibt Planned bis Cutover
Idempotent; verwendet GraphQL fuer tagged_vlans (REST liefert null).
"""
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 gql(query):
r = urllib.request.Request(f"{BASE}/graphql/", data=json.dumps({"query": query}).encode(), method="POST")
r.add_header("Authorization", f"Token {TOKEN}")
r.add_header("Content-Type", "application/json")
with urllib.request.urlopen(r, timeout=30) as resp:
data = json.loads(resp.read())
if data.get("errors"):
print("GraphQL errors:", data["errors"])
raise SystemExit(1)
return data["data"]
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"]
S_ACTIVE = sid("Active")
S_PLANNED = sid("Planned")
role_router = find("/extras/roles", name="Router")
ns_global = find("/ipam/namespaces", name="Global")["id"]
vg = find("/ipam/vlan-groups", name="BF")
V = {}
for vid in (1, 10, 20, 30, 40, 50, 90, 99, 100):
V[vid] = find("/ipam/vlans", vid=vid, vlan_group=vg["id"])
core = find("/dcim/devices", name="crs326-core")
print("== crs326-core: Rolle + Status")
patch("/dcim/devices", core, {"role": role_router["id"]}, "crs326-core role=Router")
print("== crs326-core: VLAN-Subinterfaces (L3 SVIs)")
br = find("/dcim/interfaces", device="crs326-core", name="bridge")
for vid in (10, 20, 30, 40, 50, 90, 99, 100):
ifname = f"vlan{vid}"
desc = f"L3 SVI VLAN{vid}" if vid != 99 else "L3 SVI VLAN99 (Mgmt + Router-GW)"
obj = goc("/dcim/interfaces", {"device": core["id"], "name": ifname},
{"device": core["id"], "name": ifname, "type": "virtual",
"status": S_ACTIVE, "description": desc,
"mode": "tagged", "parent_interface": br["id"],
"tagged_vlans": [V[vid]["id"]]}, label=f"core:{ifname}")
if not DRY and not str(obj["id"]).startswith("DRY"):
# Nachträglich Parent/Mode/VLAN sicherstellen (goc legt nur an, patch bei Änderung)
req("PATCH", f"/dcim/interfaces/{obj['id']}/",
{"parent_interface": br["id"], "mode": "tagged",
"tagged_vlans": [V[vid]["id"]]})
print("== crs326-core: GW-IPs je VLAN (v10..v99)")
IF = {}
for vid in (10, 20, 30, 40, 50, 90, 99):
IF[vid] = find("/dcim/interfaces", device="crs326-core", name=f"vlan{vid}")
for vid in (10, 20, 30, 40, 50, 90, 99):
addr = f"10.40.{vid}.1/24"
desc = f"VLAN{vid} GW (core, staged)"
ip = find("/ipam/ip-addresses", address=addr)
if not ip:
ip = goc("/ipam/ip-addresses", {"address": addr},
{"address": addr, "status": S_ACTIVE, "description": desc,
"namespace": ns_global}, label=addr)
if DRY or str(ip["id"]).startswith("DRY"):
continue
ex = find("/dcim/interfaces", device="crs326-core", name=f"vlan{vid}", ip_addresses=ip["id"])
if not ex:
try:
req("POST", "/ipam/ip-address-to-interface/",
{"ip_address": ip["id"], "interface": IF[vid]["id"]})
log.append(f" + {addr} -> core:vlan{vid}")
except urllib.error.HTTPError:
log.append(f" = {addr} -> core:vlan{vid} (bereits)")
print("== crs326-core: Kommentar (L3-Router-Ziel)")
if not DRY:
req("PATCH", f"/dcim/devices/{core['id']}/",
{"comments": "TP15 2026-07-23: grundkonfiguriert via REST (Bring-up-Level). "
"MAC 04:F4:1C:B4:17:76, software-id 4W04-URHT, Serial HKA0AREXJFM. "
"Wird neuer L3-Router (Routing/DHCP/DNS/NAT von ap1 -> core). "
"Mgmt 10.40.99.11 (VLAN99) + GW-IPs .1 je Client-VLAN staged. "
"VLAN1-Temp 10.40.41.211. Status Planned bis Cutover."})
print("\n".join(log))
print(f"\n{'DRY-RUN - nichts geschrieben. Mit --apply ausfuehren.' if DRY else 'ANGEWENDET.'}")

View File

@@ -0,0 +1,126 @@
# Nautobot Golden Config -- intended RouterOS config for {{ obj.name }}
# Generated from Nautobot device data + hardcoded TP15 L3 staged values.
# Scope: identity for all MikroTik devices; full staged L3 cutover config for crs326-core.
/system identity set name={{ obj.name }}
{% if obj.name == "crs326-core" %}
# =====================================================================================
# TP15 — crs326-core L3 staged config (ap1 -> core). NICHT automatisch aktivieren.
# Alles Betriebsumlenkende wird disabled=yes angelegt; Cutover aktiviert in einem Rutsch.
# =====================================================================================
# [1] INTERFACE-LISTS
/interface/list add name=WAN comment="TP15"
/interface/list add name=LAN comment="TP15"
/interface/list/member add list=WAN interface=vlan100
/interface/list/member add list=LAN interface=vlan10
/interface/list/member add list=LAN interface=vlan20
/interface/list/member add list=LAN interface=vlan30
/interface/list/member add list=LAN interface=vlan40
/interface/list/member add list=LAN interface=vlan50
/interface/list/member add list=LAN interface=vlan90
/interface/list/member add list=LAN interface=vlan99
# [2] GW-IPs je VLAN — STAGED (disabled=yes)
/ip/address
{% for iface in obj.interfaces.all() %}
{% if iface.name.startswith("vlan") %}
{% for ip in iface.ip_addresses.all() %}
{% if ip.host == "10.40." + iface.name|replace("vlan", "") + ".1" %}
add address={{ ip.address }} interface={{ iface.name }} comment="{{ iface.description }}" disabled=yes
{% endif %}
{% endfor %}
{% endif %}
{% endfor %}
# [3] WAN DHCP-Client (Liwest/Arris) — STAGED
/ip/dhcp-client add interface=vlan100 use-peer-dns=no add-default-route=yes \
comment="TP15 WAN Liwest (Arris)" disabled=yes
# [4] DHCP-Server je VLAN
/ip/pool add name=pool-v10 ranges=10.40.10.100-10.40.10.200
/ip/pool add name=pool-v20 ranges=10.40.20.100-10.40.20.200
/ip/pool add name=pool-v30 ranges=10.40.30.100-10.40.30.250
/ip/pool add name=pool-v40 ranges=10.40.40.100-10.40.40.200
/ip/pool add name=pool-v50 ranges=10.40.50.100-10.40.50.200
/ip/pool add name=pool-v90 ranges=10.40.90.100-10.40.90.200
/ip/pool add name=pool-v99 ranges=10.40.99.100-10.40.99.200
/ip/dhcp-server/network add address=10.40.10.0/24 gateway=10.40.10.1 dns-server=10.40.10.1 domain=bf.lan
/ip/dhcp-server/network add address=10.40.20.0/24 gateway=10.40.20.1 dns-server=10.40.20.1 domain=bf.lan
/ip/dhcp-server/network add address=10.40.30.0/24 gateway=10.40.30.1 dns-server=10.40.30.1 domain=bf.lan
/ip/dhcp-server/network add address=10.40.40.0/24 gateway=10.40.40.1 dns-server=10.40.40.1 domain=bf.lan
/ip/dhcp-server/network add address=10.40.50.0/24 gateway=10.40.50.1 dns-server=10.40.50.1 domain=bf.lan
/ip/dhcp-server/network add address=10.40.90.0/24 gateway=10.40.90.1 dns-server=10.40.90.1 domain=bf.lan
/ip/dhcp-server/network add address=10.40.99.0/24 gateway=10.40.99.1 dns-server=10.40.99.1 domain=bf.lan
/ip/dhcp-server add name=dhcp-v10 interface=vlan10 address-pool=pool-v10 lease-time=1d add-dns-entries-suffix=lan comment=MAIN disabled=yes
/ip/dhcp-server add name=dhcp-v20 interface=vlan20 address-pool=pool-v20 lease-time=1d add-dns-entries-suffix=lan comment=INFRA disabled=yes
/ip/dhcp-server add name=dhcp-v30 interface=vlan30 address-pool=pool-v30 lease-time=1d add-dns-entries-suffix=lan comment=IOT disabled=yes
/ip/dhcp-server add name=dhcp-v40 interface=vlan40 address-pool=pool-v40 lease-time=1d add-dns-entries-suffix=lan comment=MEDIA disabled=yes
/ip/dhcp-server add name=dhcp-v50 interface=vlan50 address-pool=pool-v50 lease-time=1d add-dns-entries-suffix=lan comment=UNTRUSTED disabled=yes
/ip/dhcp-server add name=dhcp-v90 interface=vlan90 address-pool=pool-v90 lease-time=12h add-dns-entries-suffix=lan comment=GUEST disabled=yes
/ip/dhcp-server add name=dhcp-v99 interface=vlan99 address-pool=pool-v99 lease-time=1d add-dns-entries-suffix=lan comment=MGMT disabled=yes
# [5] DNS
/ip/dns set allow-remote-requests=yes servers=1.1.1.1 \
use-doh-server=https://px35ynlol3.cloudflare-gateway.com/dns-query verify-doh-cert=yes \
mdns-repeat-ifaces=vlan10,vlan20,vlan40,vlan30,vlan90
/ip/dns/static add name=router.lan address=10.40.99.1 comment="core = GW/Router"
/ip/dns/static add name=ap1.bf.lan address=10.40.99.3 comment="ap1 = reiner AP"
/ip/dns/static add name=ap2.bf.lan address=10.40.99.2 comment="ap2 = reiner AP"
/ip/dns/static add name=core.bf.lan address=10.40.99.1 comment="crs326-core (L3)"
/ip/dns/static add name=branch.bf.lan address=10.40.99.12 comment="crs326-branch (nach Renumber .13->.12)"
/ip/dns/static add name=macmini.bf.lan address=10.40.20.167
/ip/dns/static add name=pve.bf.lan address=10.40.20.100
/ip/dns/static add name=pve-mcp.bf.lan address=10.40.20.173
/ip/dns/static add name=nas.bf.lan address=10.40.20.10
/ip/dns/static add name=docker.bf.lan address=10.40.20.126
/ip/dns/static add name=platform-pg.bf.lan address=10.40.20.20
# [6] NAT
/ip/firewall/nat add chain=srcnat dst-address=10.40.0.0/16 action=accept \
comment="TP4: no-NAT inter-VLAN (vor masquerade)"
/ip/firewall/nat add chain=srcnat out-interface-list=WAN action=masquerade \
comment="defconf: masquerade WAN"
# [7] FIREWALL FILTER
/ip/firewall/filter add chain=forward action=accept connection-state=established,related,untracked comment="defconf: accept est,rel,untracked"
/ip/firewall/filter add chain=forward action=fasttrack-connection connection-state=established,related comment="defconf: fasttrack"
/ip/firewall/filter add chain=forward action=drop connection-state=invalid comment="defconf: drop invalid"
/ip/firewall/filter add chain=forward action=drop connection-nat-state=!dstnat in-interface-list=WAN comment="defconf: drop WAN !dstnat"
/ip/firewall/filter add chain=forward action=accept in-interface=wg-hub comment="TP10: allow from WG spokes (FM/DW)"
/ip/firewall/filter add chain=forward action=accept out-interface=wg-hub comment="TP10: allow to WG spokes (FM/DW)"
/ip/firewall/filter add chain=forward action=accept in-interface=vlan10 out-interface=vlan40 comment="TP5: MAIN->MEDIA (AirPlay/Sonos)"
/ip/firewall/filter add chain=forward action=accept in-interface=vlan40 out-interface=vlan10 comment="TP5: MEDIA->MAIN"
/ip/firewall/filter add chain=forward action=accept in-interface=vlan20 out-interface=vlan40 comment="TP5: INFRA->MEDIA (HA<->Sonos)"
/ip/firewall/filter add chain=forward action=accept in-interface=vlan40 out-interface=vlan20 comment="TP5: MEDIA->INFRA"
/ip/firewall/filter add chain=forward action=accept in-interface=vlan90 out-interface=vlan40 comment="TP5: GUEST->MEDIA (Sonos/Cast)"
/ip/firewall/filter add chain=forward action=accept in-interface=vlan40 out-interface=vlan90 comment="TP5: MEDIA->GUEST"
/ip/firewall/filter add chain=input action=accept connection-state=established,related,untracked comment="TP10-seal-est"
/ip/firewall/filter add chain=input action=drop connection-state=invalid comment="TP10-seal-inv"
/ip/firewall/filter add chain=input action=accept protocol=udp dst-port=51821 comment="TP10: WG hub listen"
/ip/firewall/filter add chain=input action=accept protocol=udp dst-port=68 in-interface-list=WAN comment="TP10-seal-dhcp"
/ip/firewall/filter add chain=input action=accept protocol=icmp comment="TP10-seal-icmp"
/ip/firewall/filter add chain=input action=accept in-interface-list=LAN comment="TP10-seal-lan"
/ip/firewall/filter add chain=input action=drop in-interface-list=WAN comment="TP10-seal-wandrop"
/ip/firewall/filter add chain=input action=accept protocol=udp dst-port=5353 in-interface=vlan10 comment="TP5: mDNS in vlan10"
/ip/firewall/filter add chain=input action=accept protocol=udp dst-port=5353 in-interface=vlan40 comment="TP5: mDNS in vlan40"
/ip/firewall/filter add chain=input action=accept protocol=udp dst-port=5353 in-interface=vlan20 comment="TP5: mDNS in vlan20"
/ip/firewall/filter add chain=input action=accept protocol=udp dst-port=5353 in-interface=vlan30 comment="TP5: mDNS in vlan30"
/ip/firewall/filter add chain=input action=accept protocol=udp dst-port=5353 in-interface=vlan90 comment="TP5: mDNS in vlan90"
# [8] WG-HUB (TP10 Multi-Site) — private-key wird im Cutover-Fenster gesetzt
/interface/wireguard add name=wg-hub mtu=1412 listen-port=51821 private-key="<<< AP1_WG_HUB_PRIVKEY >>>" \
comment="TP10 Multi-Site VPN Hub (FM/DW spokes)" disabled=yes
/interface/wireguard/peers add interface=wg-hub name=fm allowed-address=10.101.0.0/24 \
public-key="dzRY0lnnUygijChWe5wXZmXPeNcbdALpqBptA3fyiVE=" \
endpoint-address=abxhutfgsid9snmy.myfritz.net endpoint-port=52476
/interface/wireguard/peers add interface=wg-hub name=dw allowed-address=10.95.10.0/24 \
public-key="FqYZLztQ/qME5VDie5QLGH++5299WJiE1dPpQnPbzUw="
/ip/route add dst-address=10.101.0.0/24 gateway=wg-hub comment="TP10: FM spoke -> wg-hub"
/ip/route add dst-address=10.95.10.0/24 gateway=wg-hub comment="TP10: DW spoke -> wg-hub"
{% endif %}

View File

@@ -0,0 +1,10 @@
ap1 Cutover-Export vom 2026-08-05T22:44:30+02:00
DHCP-Leases gesamt: 59
statisch (dynamic=no): 54
dynamisch: 5
WG-Hub private-key erfasst: ja
Nächste Schritte im Cutover-Fenster:
1. ap1-leases-export.json filtern: nur dynamic=no, server-Namen auf core dhcp-v10..v99 mappen.
2. ap1-wg-hub.privkey in core-Config einsetzen (ersetzt <<< AP1_WG_HUB_PRIVKEY >>>).
3. Generate Intended Config für crs326-core erneut laufen lassen -> Nautobot-Deployment.

View File

@@ -0,0 +1,59 @@
# ap1 static leases -> crs326-core (STAGED, disabled=yes)
# Quelle: ap1-leases-export.json | 54 statische Leases
/ip/dhcp-server/lease add server=dhcp-v10 mac-address="F2:62:4C:E1:AF:09" address="10.40.10.196" client-id="1:f2:62:4c:e1:af:9" comment="iPhone" disabled=yes
/ip/dhcp-server/lease add server=dhcp-v10 mac-address="56:71:6C:22:E6:A9" address="10.40.10.82" client-id="1:56:71:6c:22:e6:a9" dhcp-option='dns 1.1.1.1' comment="Mac" disabled=yes
/ip/dhcp-server/lease add server=dhcp-v20 mac-address="90:09:D0:58:20:F9" address="10.40.20.10" comment="nas-bf" disabled=yes
/ip/dhcp-server/lease add server=dhcp-v30 mac-address="FC:EE:11:03:32:26" address="10.40.30.150" client-id="1:fc:ee:11:3:32:26" comment="ingenic-3226 / Ender3V3KE-3226" disabled=yes
/ip/dhcp-server/lease add server=dhcp-v30 mac-address="E4:BC:96:1F:B2:81" address="10.40.30.180" client-id="1:e4:bc:96:1f:b2:81" dhcp-option='dns 1.1.1.1' comment="espressif / Airfryer" disabled=yes
/ip/dhcp-server/lease add server=dhcp-v30 mac-address="A0:85:E3:C8:65:34" address="10.40.30.202" comment="shelly-s22_kueche_steckdose3-2" disabled=yes
/ip/dhcp-server/lease add server=dhcp-v30 mac-address="AC:15:18:7B:62:20" address="10.40.30.203" comment="shelly-s23_dieleug-wohnzimmer_licht" disabled=yes
/ip/dhcp-server/lease add server=dhcp-v30 mac-address="14:33:5C:D8:2D:7C" address="10.40.30.204" comment="shelly-s24_bad_spiegelschrank" disabled=yes
/ip/dhcp-server/lease add server=dhcp-v30 mac-address="98:A3:16:79:38:50" address="10.40.30.205" comment="shelly-s25_bad_lichtschalter" disabled=yes
/ip/dhcp-server/lease add server=dhcp-v30 mac-address="98:A3:16:7C:5F:EC" address="10.40.30.206" comment="shelly-s20_kueche_steckdosen2" disabled=yes
/ip/dhcp-server/lease add server=dhcp-v30 mac-address="20:6E:F1:01:F9:F8" address="10.40.30.207" comment="shelly-s21_kueche_steckdosen4" disabled=yes
/ip/dhcp-server/lease add server=dhcp-v30 mac-address="E4:B0:63:D7:C7:A4" address="10.40.30.208" comment="shelly-s1_weihnachten_kueche" disabled=yes
/ip/dhcp-server/lease add server=dhcp-v30 mac-address="28:37:2F:36:F4:7C" address="10.40.30.209" comment="shelly-s2_weihnachten_terrasse" disabled=yes
/ip/dhcp-server/lease add server=dhcp-v30 mac-address="E4:B0:63:E4:EF:7C" address="10.40.30.210" comment="shelly-s3_weihnachten_kinderzimmer" disabled=yes
/ip/dhcp-server/lease add server=dhcp-v30 mac-address="E4:B0:63:E4:0F:30" address="10.40.30.211" comment="shelly-s4_kueche_mikrowelle" disabled=yes
/ip/dhcp-server/lease add server=dhcp-v30 mac-address="D8:85:AC:0C:5C:C8" address="10.40.30.212" comment="shelly-s5_kueche_backofen" disabled=yes
/ip/dhcp-server/lease add server=dhcp-v30 mac-address="D0:CF:13:D8:18:FC" address="10.40.30.213" comment="shelly-s6_kueche_kuehlschrank" disabled=yes
/ip/dhcp-server/lease add server=dhcp-v30 mac-address="D0:CF:13:D9:94:E8" address="10.40.30.214" comment="shelly-s7_kueche_tiefkuehlschrank" disabled=yes
/ip/dhcp-server/lease add server=dhcp-v30 mac-address="E4:B0:63:FA:FD:E0" address="10.40.30.215" comment="shelly-s8_kueche_geschirrspueler" disabled=yes
/ip/dhcp-server/lease add server=dhcp-v30 mac-address="E8:F6:0A:61:88:08" address="10.40.30.216" comment="shelly-s9_wohnzimmer_sideboard" disabled=yes
/ip/dhcp-server/lease add server=dhcp-v30 mac-address="E4:B0:63:FC:57:E4" address="10.40.30.217" comment="shelly-s10_bad_sonos" disabled=yes
/ip/dhcp-server/lease add server=dhcp-v30 mac-address="E4:B0:63:FA:6C:00" address="10.40.30.218" comment="shelly-s11_kinderzimmer_sonos" disabled=yes
/ip/dhcp-server/lease add server=dhcp-v30 mac-address="48:F6:EE:C8:E6:AC" address="10.40.30.219" comment="shelly-s12_dieleug_steckdosen" disabled=yes
/ip/dhcp-server/lease add server=dhcp-v30 mac-address="48:F6:EE:DD:6A:F4" address="10.40.30.220" comment="shelly-s13_arbeitszimmer_technik" disabled=yes
/ip/dhcp-server/lease add server=dhcp-v30 mac-address="E4:B0:63:FC:59:48" address="10.40.30.221" comment="shelly-s14_schlafzimmer_bett" disabled=yes
/ip/dhcp-server/lease add server=dhcp-v30 mac-address="E4:B0:63:FA:D5:34" address="10.40.30.222" comment="shelly-s15_technik_pve" disabled=yes
/ip/dhcp-server/lease add server=dhcp-v30 mac-address="10:20:BA:FA:F5:40" address="10.40.30.223" comment="shelly-s16_wohnzimmer_steckdosen" disabled=yes
/ip/dhcp-server/lease add server=dhcp-v30 mac-address="10:20:BA:FB:6D:64" address="10.40.30.224" comment="shelly-s17_bad_steckdosen" disabled=yes
/ip/dhcp-server/lease add server=dhcp-v30 mac-address="48:F6:EE:CC:16:E4" address="10.40.30.225" comment="shelly-s18_kueche_steckdosen1" disabled=yes
/ip/dhcp-server/lease add server=dhcp-v30 mac-address="10:20:BA:FB:70:FC" address="10.40.30.226" comment="shelly-s19_kueche_steckdosen3" disabled=yes
/ip/dhcp-server/lease add server=dhcp-v30 mac-address="B8:F8:62:09:F2:04" address="10.40.30.227" comment="SwitchBot-floor-lamp / switchbot-floorlamp" disabled=yes
/ip/dhcp-server/lease add server=dhcp-v30 mac-address="98:F4:AB:2C:A7:88" address="10.40.30.228" comment="Twinkly-2CA789 / Twinkly_Weihnachtsgirlande" disabled=yes
/ip/dhcp-server/lease add server=dhcp-v30 mac-address="00:24:E4:40:2A:72" address="10.40.30.230" comment="Withings_waage" disabled=yes
/ip/dhcp-server/lease add server=dhcp-v30 mac-address="A4:7E:FA:18:2C:64" address="10.40.30.231" comment="Withings_blutdruck" disabled=yes
/ip/dhcp-server/lease add server=dhcp-v30 mac-address="BC:24:11:8E:0A:57" address="10.40.30.232" comment="pve-homematic-raspi-ccu" disabled=yes
/ip/dhcp-server/lease add server=dhcp-v30 mac-address="98:A3:16:7A:A3:54" address="10.40.30.233" comment="shelly-s26_bad_luefter" disabled=yes
/ip/dhcp-server/lease add server=dhcp-v30 mac-address="A4:F0:0F:5F:D0:EC" address="10.40.30.236" comment="lichterkette" disabled=yes
/ip/dhcp-server/lease add server=dhcp-v30 mac-address="24:DC:C3:9B:89:64" address="10.40.30.237" comment="lichterkette-mini" disabled=yes
/ip/dhcp-server/lease add server=dhcp-v30 mac-address="28:DB:A7:09:7B:54" address="10.40.30.241" comment="NukiSmartlock" disabled=yes
/ip/dhcp-server/lease add server=dhcp-v30 mac-address="24:62:AB:B1:63:F4" address="10.40.30.244" comment="NukiBridge" disabled=yes
/ip/dhcp-server/lease add server=dhcp-v30 mac-address="4C:EB:BD:E3:25:F2" address="10.40.30.5" comment="P750W / P750W" disabled=yes
/ip/dhcp-server/lease add server=dhcp-v30 mac-address="54:F2:9F:12:51:F0" address="10.40.30.8" comment="DTUBI-414392337823 / DTUBI-414392337823" disabled=yes
/ip/dhcp-server/lease add server=dhcp-v40 mac-address="70:AF:24:B8:C9:86" address="10.40.40.11" comment="tv" disabled=yes
/ip/dhcp-server/lease add server=dhcp-v40 mac-address="34:7E:5C:32:37:96" address="10.40.40.191" client-id="1:34:7e:5c:32:37:96" comment="SonosZP" disabled=yes
/ip/dhcp-server/lease add server=dhcp-v40 mac-address="34:7E:5C:32:33:B8" address="10.40.40.192" client-id="1:34:7e:5c:32:33:b8" comment="SonosZP" disabled=yes
/ip/dhcp-server/lease add server=dhcp-v40 mac-address="78:28:CA:80:2A:F6" address="10.40.40.193" client-id="1:78:28:ca:80:2a:f6" comment="SonosZP" disabled=yes
/ip/dhcp-server/lease add server=dhcp-v40 mac-address="48:A6:B8:14:FE:D0" address="10.40.40.194" client-id="1:48:a6:b8:14:fe:d0" comment="SonosZP" disabled=yes
/ip/dhcp-server/lease add server=dhcp-v40 mac-address="78:28:CA:82:16:C2" address="10.40.40.195" client-id="1:78:28:ca:82:16:c2" comment="SonosZP" disabled=yes
/ip/dhcp-server/lease add server=dhcp-v40 mac-address="48:A6:B8:16:38:AE" address="10.40.40.196" client-id="1:48:a6:b8:16:38:ae" comment="SonosZP" disabled=yes
/ip/dhcp-server/lease add server=dhcp-v40 mac-address="48:A6:B8:14:FE:F2" address="10.40.40.197" client-id="1:48:a6:b8:14:fe:f2" comment="SonosZP" disabled=yes
/ip/dhcp-server/lease add server=dhcp-v40 mac-address="78:28:CA:D2:0D:AC" address="10.40.40.198" comment="SonosZP / SonosTerrasse" disabled=yes
/ip/dhcp-server/lease add server=dhcp-v40 mac-address="48:A6:B8:0B:63:10" address="10.40.40.199" client-id="1:48:a6:b8:b:63:10" comment="SonosZP" disabled=yes
/ip/dhcp-server/lease add server=dhcp-v40 mac-address="48:A6:B8:69:69:7B" address="10.40.40.200" client-id="1:48:a6:b8:69:69:7b" comment="SonosZP" disabled=yes
/ip/dhcp-server/lease add server=dhcp-v40 mac-address="94:9F:3E:49:59:EE" address="10.40.40.201" client-id="1:94:9f:3e:49:59:ee" comment="SonosZP" disabled=yes
# Cutover: /ip/dhcp-server/lease/enable [find comment~"ap1 lease"]

View File

@@ -0,0 +1,558 @@
# 2026-08-06 06:16:12 by RouterOS 7.23.2
# software id = 2ATI-ZRRH
#
# model = C52iG-5HaxD2HaxD
# serial number = HM70BFZBYVQ
/interface bridge
add admin-mac=D0:EA:11:4D:40:E1 auto-mac=no comment=defconf name=bridge \
vlan-filtering=yes
/interface wireguard
add comment="TP10 Multi-Site VPN Hub (FM/DW spokes). Kein IP aufs IF (Routing \
via AllowedIPs/Routes)." listen-port=51821 mtu=1412 name=wg-hub
/interface vlan
add comment="MAIN 10.40.10.0/24" interface=bridge name=vlan10 vlan-id=10
add comment="INFRA 10.40.20.0/24" interface=bridge name=vlan20 vlan-id=20
add comment="IOT 10.40.30.0/24" interface=bridge name=vlan30 vlan-id=30
add comment="MEDIA 10.40.40.0/24" interface=bridge name=vlan40 vlan-id=40
add comment="UNTRUSTED 10.40.50.0/24" interface=bridge name=vlan50 vlan-id=50
add comment="GUEST 10.40.90.0/24" interface=bridge name=vlan90 vlan-id=90
add comment="MGMT 10.40.99.0/24" interface=bridge name=vlan99 vlan-id=99
add comment="WAN Arris (Liwest)" interface=bridge name=vlan100 vlan-id=100
/interface list
add comment=defconf name=WAN
add comment=defconf name=LAN
/interface wifi channel
add band=2ghz-ax disabled=no frequency=2462 name=ch-2g width=20mhz
add band=5ghz-ax disabled=no frequency=5745 name=ch-5g skip-dfs-channels=\
10min-cac width=20/40/80mhz
/interface wifi datapath
add bridge=bridge name=dp-main vlan-id=10
add bridge=bridge name=dp-iot vlan-id=30
add bridge=bridge name=dp-media vlan-id=40
add bridge=bridge name=dp-untrusted vlan-id=50
add bridge=bridge name=dp-guest vlan-id=90
add bridge=bridge name=dp-infra vlan-id=20
/interface wifi security
add authentication-types=wpa3-psk disabled=no ft=yes ft-over-ds=yes name=\
sec-main
add authentication-types=wpa2-psk,wpa3-psk disabled=no ft=no name=sec-iot
add authentication-types=wpa2-psk,wpa3-psk disabled=no ft=no ft-over-ds=no \
name=sec-media
add authentication-types=wpa2-psk,wpa3-psk disabled=no ft=no name=\
sec-untrusted
add authentication-types=wpa2-psk,wpa3-psk ft=yes ft-over-ds=yes name=\
sec-guest
add authentication-types=wpa2-psk,wpa3-psk ft=no name=sec-infra
/interface wifi configuration
add country=Austria disabled=no mode=ap name=cfg-main-2g security=sec-main \
ssid=dirtyvader-main tx-power=20
add country=Austria datapath=dp-iot mode=ap multicast-enhance=enabled name=\
cfg-iot security=sec-iot ssid=dirtyvader-iot
add country=Austria datapath=dp-media mode=ap multicast-enhance=enabled name=\
cfg-media security=sec-media ssid=dirtyvader-media
add country=Austria datapath=dp-untrusted mode=ap name=cfg-untrusted \
security=sec-untrusted ssid=dirtyvader-untrusted
add country=Austria datapath=dp-guest mode=ap name=cfg-guest security=\
sec-guest ssid=dirtyvader-guest
add country=Austria datapath=dp-infra mode=ap name=cfg-infra security=\
sec-infra ssid=dirtyvader-infra
/interface wifi
set [ find default-name=wifi2 ] channel.frequency=2412 configuration=\
cfg-main-2g configuration.manager=local disabled=no
add configuration=cfg-guest disabled=no mac-address=D2:EA:11:4D:40:EB \
master-interface=wifi2 name=w2-guest
add configuration=cfg-iot disabled=no mac-address=D2:EA:11:4D:40:E8 \
master-interface=wifi2 name=w2-iot
add configuration=cfg-media disabled=no mac-address=D2:EA:11:4D:40:E9 \
master-interface=wifi2 name=w2-media
add configuration=cfg-untrusted disabled=no mac-address=D2:EA:11:4D:40:EA \
master-interface=wifi2 name=w2-untrusted
/interface wifi steering
add name=st-main neighbor-group=bf-main rrm=yes wnm=yes
/interface wifi configuration
add channel=ch-5g country=Austria datapath=dp-main mode=ap name=cfg-main-5g \
security=sec-main ssid=dirtyvader-main steering=st-main
/interface wifi
set [ find default-name=wifi1 ] channel.frequency=5180 configuration=\
cfg-main-5g configuration.manager=local disabled=no
add configuration=cfg-guest disabled=no mac-address=D2:EA:11:4D:40:E7 \
master-interface=wifi1 name=w5-guest
add configuration=cfg-media disabled=no mac-address=D2:EA:11:4D:40:E5 \
master-interface=wifi1 name=w5-media
add configuration=cfg-untrusted disabled=no mac-address=D2:EA:11:4D:40:E6 \
master-interface=wifi1 name=w5-untrusted
/ip dhcp-server option
add code=6 name="dns 1.1.1.1" value=0x01010101
add code=138 name=omada-inform value=0x0A2814AF
/ip pool
add name=default-dhcp ranges=192.168.88.10-192.168.88.254
add name=pool-v10 ranges=10.40.10.100-10.40.10.200
add name=pool-v20 ranges=10.40.20.100-10.40.20.200
add name=pool-v30 ranges=10.40.30.100-10.40.30.250
add name=pool-v40 ranges=10.40.40.100-10.40.40.200
add name=pool-v50 ranges=10.40.50.100-10.40.50.200
add name=pool-v90 ranges=10.40.90.100-10.40.90.200
add name=pool-v99 ranges=10.40.99.100-10.40.99.200
add comment="BF flat .41 (legacy, retiring)" name=pool-v1 ranges=\
10.40.41.180-10.40.41.199
/ip dhcp-server
add address-pool=pool-v1 disabled=yes interface=bridge lease-time=2h name=\
defconf
add address-pool=pool-v10 comment=MAIN interface=vlan10 lease-time=1d name=\
dhcp-v10
add address-pool=pool-v20 comment=INFRA interface=vlan20 lease-time=1d name=\
dhcp-v20
add address-pool=pool-v30 comment=IOT interface=vlan30 lease-time=1d name=\
dhcp-v30
add address-pool=pool-v40 comment=MEDIA interface=vlan40 lease-time=1d name=\
dhcp-v40
add address-pool=pool-v50 comment=UNTRUSTED interface=vlan50 lease-time=1d \
name=dhcp-v50
add address-pool=pool-v90 comment=GUEST interface=vlan90 lease-time=12h name=\
dhcp-v90
add address-pool=pool-v99 comment=MGMT interface=vlan99 lease-time=1d name=\
dhcp-v99
/system logging action
add disk-file-name=wan-test disk-lines-per-file=2000 name=wantest target=disk
/system script
add dont-require-permissions=no name=wantest-probe owner=admin policy=\
read,write,policy,test source=" :local ts ([/system clock get date] . \" \
\" . [/system clock get time])\
\n :local e1stat \"\?\"\
\n :do { :set e1stat [/ip dhcp-client get [find interface=ether1] status]\
\_} on-error={}\
\n :local e1addr \"-\"\
\n :do { :set e1addr [/ip dhcp-client get [find interface=ether1] address\
] } on-error={}\
\n :local e1mac [/interface ethernet get [find name=ether1] mac-address]\
\n :log info (\"WANTEST [\" . \$ts . \"] ether1 status=\" . \$e1stat . \"\
\_addr=\" . \$e1addr . \" mac=\" . \$e1mac)\
\n :local gw \"-\"\
\n :do { :set gw [/ip route get [find dst-address=\"0.0.0.0/0\" active=ye\
s] gateway] } on-error={}\
\n :log info (\"WANTEST route default-gw=\" . \$gw)\
\n # WAN-Routing+NAT: Router -> public 1.1.1.1 (UDP/53). UNABH\C3\84NGIG \
von ap1-DNS-Config.\
\n # (ICMP/HTTPS sind hier geblockt -> :resolve gegen feste IP ist das ro\
buste Signal.)\
\n :do { :local w [:resolve \"google.com\" server=1.1.1.1] ; :log info (\
\"WANTEST wan-reach via-1.1.1.1 ok google.com=\" . \$w) } on-error={ :log \
info \"WANTEST wan-reach via-1.1.1.1 FAIL (Routing/NAT\?)\" }\
\n # DoH end-to-end: ap1-Resolver = was die Clients als DNS bekommen\
\n :do { :local d [:resolve \"google.com\"] ; :log info (\"WANTEST dns-do\
h ok google.com=\" . \$d) } on-error={ :log info \"WANTEST dns-doh FAIL (D\
oH/Bootstrap\?)\" }"
add dont-require-permissions=no name=wantest-run owner=admin policy=\
read,write,policy,test source=" :global WTN\
\n :if (([:typeof \$WTN] = \"nothing\") or (\$WTN = \"\")) do={ :set WTN \
24 }\
\n :log info (\"WANTEST run start (\" . \$WTN . \"x @15s, ~\" . (\$WTN * \
15 / 60) . \" min)\")\
\n :for i from=1 to=\$WTN do={\
\n /system script run wantest-probe\
\n :delay 15s\
\n }\
\n :log info \"WANTEST run done\""
add dont-require-permissions=no name=wanmac-clone owner=admin policy=\
read,write,policy,test source=" :global FBMAC\
\n :if (([:typeof \$FBMAC] = \"nothing\") or (\$FBMAC = \"\")) do={ :set \
FBMAC \"0C:72:74:36:90:99\" }\
\n :global E1ORIG\
\n :set E1ORIG [/interface ethernet get [find name=ether1] mac-address]\
\n :log info (\"WANTEST macclone: orig=\" . \$E1ORIG . \" -> fb=\" . \$FB\
MAC . \" (Auto-Revert in ~90s)\")\
\n /interface ethernet set [find name=ether1] mac-address=\$FBMAC\
\n :do { /ip dhcp-client release [find interface=ether1] } on-error={}\
\n :delay 2s\
\n :do { /ip dhcp-client renew [find interface=ether1] } on-error={}\
\n :delay 25s\
\n :for i from=1 to=4 do={ /system script run wantest-probe ; :delay 15s \
}\
\n # --- garantierter Auto-Revert im selben Lauf (kein Scheduler n\C3\B6t\
ig) ---\
\n /interface ethernet reset-mac-address [find name=ether1]\
\n :do { /ip dhcp-client release [find interface=ether1] } on-error={}\
\n :delay 2s\
\n :do { /ip dhcp-client renew [find interface=ether1] } on-error={}\
\n :delay 15s\
\n /system script run wantest-probe\
\n :log info (\"WANTEST macclone done -> revert auf factory-MAC (orig war\
\_\" . \$E1ORIG . \")\")"
add dont-require-permissions=no name=wanmac-revert owner=admin policy=\
read,write,policy,test source=" /interface ethernet reset-mac-address [fi\
nd name=ether1]\
\n :do { /ip dhcp-client release [find interface=ether1] } on-error={}\
\n :delay 2s\
\n :do { /ip dhcp-client renew [find interface=ether1] } on-error={}\
\n :delay 10s\
\n /system script run wantest-probe\
\n :log info \"WANTEST macclone manual-revert -> factory-MAC\""
/disk settings
set auto-media-interface=bridge auto-media-sharing=yes auto-smb-sharing=yes
/interface bridge port
add bridge=bridge comment=defconf interface=ether2
add bridge=bridge comment=defconf interface=ether3
add bridge=bridge comment=defconf interface=ether4
add bridge=bridge comment=defconf interface=ether5
add bridge=bridge comment=defconf interface=wifi2
add bridge=bridge interface=ether1
/interface bridge vlan
add bridge=bridge untagged=bridge,ether2 vlan-ids=1
add bridge=bridge tagged=bridge,ether2,ether1 vlan-ids=10
add bridge=bridge tagged=bridge,ether2,ether1 vlan-ids=20
add bridge=bridge tagged=bridge,ether2,ether1 vlan-ids=30
add bridge=bridge tagged=bridge,ether2,ether1 vlan-ids=40
add bridge=bridge tagged=bridge,ether2,ether1 vlan-ids=50
add bridge=bridge tagged=bridge,ether2,ether1 vlan-ids=90
add bridge=bridge tagged=bridge,ether2,ether1 vlan-ids=99
add bridge=bridge tagged=bridge,ether2,ether1 vlan-ids=100
/interface list member
add comment=defconf interface=bridge list=LAN
add comment=defconf interface=ether1 list=WAN
add comment="WAN Arris via vlan100" interface=vlan100 list=WAN
/interface wifi access-list
add action=accept comment="pin P750W to ap1 (1/2): accept on ap1 iot" \
disabled=yes interface=w2-iot mac-address=4C:EB:BD:E3:25:F2
add action=reject comment=\
"pin P750W to ap1 (2/2): reject on any other AP incl dynamic ap2 VAPs" \
disabled=yes mac-address=4C:EB:BD:E3:25:F2
/interface wifi cap
set caps-man-addresses=10.40.41.1 discovery-interfaces=bridge
/interface wifi capsman
set enabled=yes interfaces=bridge
/interface wifi provisioning
add action=create-dynamic-enabled master-configuration=cfg-main-2g \
slave-configurations=cfg-iot,cfg-media,cfg-untrusted,cfg-guest,cfg-infra \
supported-bands=2ghz-ax
add action=create-dynamic-enabled master-configuration=cfg-main-5g \
slave-configurations=cfg-media,cfg-untrusted,cfg-guest,cfg-infra \
supported-bands=5ghz-ax
/interface wireguard peers
add allowed-address=10.101.0.0/24 comment=fm endpoint-address=\
abxhutfgsid9snmy.myfritz.net endpoint-port=52476 interface=wg-hub name=fm \
public-key="dzRY0lnnUygijChWe5wXZmXPeNcbdALpqBptA3fyiVE="
add allowed-address=10.95.10.0/24 comment=dw interface=wg-hub name=dw \
public-key="FqYZLztQ/qME5VDie5QLGH++5299WJiE1dPpQnPbzUw="
/ip address
add address=10.40.41.1/24 comment=defconf interface=bridge network=10.40.41.0
add address=10.40.10.1/24 comment="MAIN GW" interface=vlan10 network=\
10.40.10.0
add address=10.40.20.1/24 comment="INFRA GW" interface=vlan20 network=\
10.40.20.0
add address=10.40.30.1/24 comment="IOT GW" interface=vlan30 network=\
10.40.30.0
add address=10.40.40.1/24 comment="MEDIA GW" interface=vlan40 network=\
10.40.40.0
add address=10.40.50.1/24 comment="UNTRUSTED GW" interface=vlan50 network=\
10.40.50.0
add address=10.40.90.1/24 comment="GUEST GW" interface=vlan90 network=\
10.40.90.0
add address=10.40.99.1/24 comment="MGMT GW" interface=vlan99 network=\
10.40.99.0
add address=10.40.41.254/24 comment="Secondary = alte FB-GW-IP; haelt statisch\
e .41-Hosts online nach Arris-Swap (ap1 NATs + WG-Hub)" interface=bridge \
network=10.40.41.0
/ip dhcp-client
add comment=defconf disabled=yes interface=ether1 name=client1 use-peer-dns=\
no
add comment="WAN Arris DHCP (Liwest) via vlan100" interface=vlan100 name=\
client2 use-peer-dns=no use-peer-ntp=no
/ip dhcp-server lease
add address=10.40.10.82 client-id=1:56:71:6c:22:e6:a9 dhcp-option=\
"dns 1.1.1.1" mac-address=56:71:6C:22:E6:A9 server=dhcp-v10
add address=10.40.10.196 client-id=1:f2:62:4c:e1:af:9 mac-address=\
F2:62:4C:E1:AF:09 server=dhcp-v10
add address=10.40.40.199 client-id=1:48:a6:b8:b:63:10 mac-address=\
48:A6:B8:0B:63:10 server=dhcp-v40
add address=10.40.40.191 client-id=1:34:7e:5c:32:37:96 mac-address=\
34:7E:5C:32:37:96 server=dhcp-v40
add address=10.40.40.192 client-id=1:34:7e:5c:32:33:b8 mac-address=\
34:7E:5C:32:33:B8 server=dhcp-v40
add address=10.40.40.193 client-id=1:78:28:ca:80:2a:f6 mac-address=\
78:28:CA:80:2A:F6 server=dhcp-v40
add address=10.40.40.195 client-id=1:78:28:ca:82:16:c2 mac-address=\
78:28:CA:82:16:C2 server=dhcp-v40
add address=10.40.40.201 client-id=1:94:9f:3e:49:59:ee mac-address=\
94:9F:3E:49:59:EE server=dhcp-v40
add address=10.40.40.196 client-id=1:48:a6:b8:16:38:ae mac-address=\
48:A6:B8:16:38:AE server=dhcp-v40
add address=10.40.40.197 client-id=1:48:a6:b8:14:fe:f2 mac-address=\
48:A6:B8:14:FE:F2 server=dhcp-v40
add address=10.40.40.200 client-id=1:48:a6:b8:69:69:7b mac-address=\
48:A6:B8:69:69:7B server=dhcp-v40
add address=10.40.40.194 client-id=1:48:a6:b8:14:fe:d0 mac-address=\
48:A6:B8:14:FE:D0 server=dhcp-v40
add address=10.40.40.198 comment=SonosTerrasse mac-address=78:28:CA:D2:0D:AC \
server=dhcp-v40
add address=10.40.30.202 comment=shelly-s22_kueche_steckdose3-2 mac-address=\
A0:85:E3:C8:65:34 server=dhcp-v30
add address=10.40.30.203 comment=shelly-s23_dieleug-wohnzimmer_licht \
mac-address=AC:15:18:7B:62:20 server=dhcp-v30
add address=10.40.30.205 comment=shelly-s25_bad_lichtschalter mac-address=\
98:A3:16:79:38:50 server=dhcp-v30
add address=10.40.30.206 comment=shelly-s20_kueche_steckdosen2 mac-address=\
98:A3:16:7C:5F:EC server=dhcp-v30
add address=10.40.30.207 comment=shelly-s21_kueche_steckdosen4 mac-address=\
20:6E:F1:01:F9:F8 server=dhcp-v30
add address=10.40.30.208 comment=shelly-s1_weihnachten_kueche mac-address=\
E4:B0:63:D7:C7:A4 server=dhcp-v30
add address=10.40.30.209 comment=shelly-s2_weihnachten_terrasse mac-address=\
28:37:2F:36:F4:7C server=dhcp-v30
add address=10.40.30.210 comment=shelly-s3_weihnachten_kinderzimmer \
mac-address=E4:B0:63:E4:EF:7C server=dhcp-v30
add address=10.40.30.211 comment=shelly-s4_kueche_mikrowelle mac-address=\
E4:B0:63:E4:0F:30 server=dhcp-v30
add address=10.40.30.212 comment=shelly-s5_kueche_backofen mac-address=\
D8:85:AC:0C:5C:C8 server=dhcp-v30
add address=10.40.30.213 comment=shelly-s6_kueche_kuehlschrank mac-address=\
D0:CF:13:D8:18:FC server=dhcp-v30
add address=10.40.30.214 comment=shelly-s7_kueche_tiefkuehlschrank \
mac-address=D0:CF:13:D9:94:E8 server=dhcp-v30
add address=10.40.30.215 comment=shelly-s8_kueche_geschirrspueler \
mac-address=E4:B0:63:FA:FD:E0 server=dhcp-v30
add address=10.40.30.216 comment=shelly-s9_wohnzimmer_sideboard mac-address=\
E8:F6:0A:61:88:08 server=dhcp-v30
add address=10.40.30.217 comment=shelly-s10_bad_sonos mac-address=\
E4:B0:63:FC:57:E4 server=dhcp-v30
add address=10.40.30.218 comment=shelly-s11_kinderzimmer_sonos mac-address=\
E4:B0:63:FA:6C:00 server=dhcp-v30
add address=10.40.30.219 comment=shelly-s12_dieleug_steckdosen mac-address=\
48:F6:EE:C8:E6:AC server=dhcp-v30
add address=10.40.30.220 comment=shelly-s13_arbeitszimmer_technik \
mac-address=48:F6:EE:DD:6A:F4 server=dhcp-v30
add address=10.40.30.221 comment=shelly-s14_schlafzimmer_bett mac-address=\
E4:B0:63:FC:59:48 server=dhcp-v30
add address=10.40.30.222 comment=shelly-s15_technik_pve mac-address=\
E4:B0:63:FA:D5:34 server=dhcp-v30
add address=10.40.30.223 comment=shelly-s16_wohnzimmer_steckdosen \
mac-address=10:20:BA:FA:F5:40 server=dhcp-v30
add address=10.40.30.224 comment=shelly-s17_bad_steckdosen mac-address=\
10:20:BA:FB:6D:64 server=dhcp-v30
add address=10.40.30.225 comment=shelly-s18_kueche_steckdosen1 mac-address=\
48:F6:EE:CC:16:E4 server=dhcp-v30
add address=10.40.30.226 comment=shelly-s19_kueche_steckdosen3 mac-address=\
10:20:BA:FB:70:FC server=dhcp-v30
add address=10.40.30.227 comment=switchbot-floorlamp mac-address=\
B8:F8:62:09:F2:04 server=dhcp-v30
add address=10.40.30.228 comment=Twinkly_Weihnachtsgirlande mac-address=\
98:F4:AB:2C:A7:88 server=dhcp-v30
add address=10.40.30.230 comment=Withings_waage mac-address=00:24:E4:40:2A:72 \
server=dhcp-v30
add address=10.40.30.231 comment=Withings_blutdruck mac-address=\
A4:7E:FA:18:2C:64 server=dhcp-v30
add address=10.40.30.232 comment=pve-homematic-raspi-ccu mac-address=\
BC:24:11:8E:0A:57 server=dhcp-v30
add address=10.40.30.233 comment=shelly-s26_bad_luefter mac-address=\
98:A3:16:7A:A3:54 server=dhcp-v30
add address=10.40.30.236 comment=lichterkette mac-address=A4:F0:0F:5F:D0:EC \
server=dhcp-v30
add address=10.40.30.237 comment=lichterkette-mini mac-address=\
24:DC:C3:9B:89:64 server=dhcp-v30
add address=10.40.30.241 comment=NukiSmartlock mac-address=28:DB:A7:09:7B:54 \
server=dhcp-v30
add address=10.40.30.244 comment=NukiBridge mac-address=24:62:AB:B1:63:F4 \
server=dhcp-v30
add address=10.40.30.8 comment=DTUBI-414392337823 mac-address=\
54:F2:9F:12:51:F0 server=dhcp-v30
add address=10.40.20.10 comment=nas-bf mac-address=90:09:D0:58:20:F9 server=\
dhcp-v20
add address=10.40.40.11 comment=tv mac-address=70:AF:24:B8:C9:86 server=\
dhcp-v40
add address=10.40.30.5 comment=P750W mac-address=4C:EB:BD:E3:25:F2 server=\
dhcp-v30
add address=10.40.30.180 client-id=1:e4:bc:96:1f:b2:81 comment=Airfryer \
dhcp-option="dns 1.1.1.1" mac-address=E4:BC:96:1F:B2:81 server=dhcp-v30
add address=10.40.30.204 comment=shelly-s24_bad_spiegelschrank mac-address=\
14:33:5C:D8:2D:7C server=dhcp-v30
add address=10.40.30.150 client-id=1:fc:ee:11:3:32:26 comment=Ender3V3KE-3226 \
mac-address=FC:EE:11:03:32:26 server=dhcp-v30
/ip dhcp-server network
add address=10.40.10.0/24 comment=MAIN dns-server=10.40.10.1 gateway=\
10.40.10.1
add address=10.40.20.0/24 comment=INFRA dns-server=10.40.20.1 gateway=\
10.40.20.1
add address=10.40.30.0/24 comment=IOT dns-server=10.40.30.1 gateway=\
10.40.30.1
add address=10.40.40.0/24 comment=MEDIA dns-server=10.40.40.1 gateway=\
10.40.40.1
add address=10.40.41.0/24 comment=defconf dns-server=10.40.41.1 gateway=\
10.40.41.1 netmask=24
add address=10.40.50.0/24 comment=UNTRUSTED dns-server=10.40.50.1 gateway=\
10.40.50.1
add address=10.40.90.0/24 comment=GUEST dns-server=10.40.90.1 gateway=\
10.40.90.1
add address=10.40.99.0/24 comment=MGMT dhcp-option=omada-inform dns-server=\
10.40.99.1 gateway=10.40.99.1
/ip dns
set allow-remote-requests=yes mdns-repeat-ifaces=\
vlan10,vlan20,vlan40,vlan30,vlan90,bridge servers=1.1.1.1 use-doh-server=\
https://px35ynlol3.cloudflare-gateway.com/dns-query verify-doh-cert=yes
/ip dns static
add address=10.40.41.1 comment=defconf name=router.lan type=A
add address=10.40.41.2 comment=\
"TP4-transition; update to 10.40.99.2 after MGMT migration" name=\
ap2.bf.lan type=A
add address=10.40.41.11 comment="ALLNET core switch" name=sw1.bf.lan type=A
add address=10.40.41.12 comment="Omada access switch" name=sw2.bf.lan type=A
add address=10.40.20.167 comment=\
"Mac mini (INFRA VLAN20, migriert 2026-06-16)" name=macmini.bf.lan type=A
add address=10.40.20.100 comment="Proxmox hypervisor" name=pve.bf.lan type=A
add address=10.40.20.173 comment="pve-mcp SSE server" name=pve-mcp.bf.lan \
type=A
add address=10.40.20.10 comment=\
"DS923+ NAS (VLAN20, TP14.1, DNS-Sweep TP14.3 2026-07-16)" name=\
nas.bf.lan type=A
add address=10.40.20.126 comment=\
"Docker-LXC / Portainer (VLAN20, TP14.2, DNS-Sweep TP14.3 2026-07-16)" \
name=docker.bf.lan type=A
add address=10.40.99.1 comment=\
"TP19.1: ap1 MGMT = vlan99 GW (live since TP4), no separate move needed" \
name=ap1.bf.lan type=A
add address=10.40.20.20 comment=\
"TP20: Platform-Postgres LXC 105 on pve, PG16, nautobot DB" name=\
platform-pg.bf.lan type=A
/ip firewall filter
add action=accept chain=forward comment="defconf: accept in ipsec policy" \
ipsec-policy=in,ipsec
add action=accept chain=forward comment="defconf: accept out ipsec policy" \
ipsec-policy=out,ipsec
add action=fasttrack-connection chain=forward comment="defconf: fasttrack" \
connection-state=established,related
add action=accept chain=forward comment=\
"defconf: accept established,related, untracked" connection-state=\
established,related,untracked
add action=drop chain=forward comment="defconf: drop invalid" \
connection-state=invalid
add action=drop chain=forward comment=\
"defconf: drop all from WAN not DSTNATed" connection-nat-state=!dstnat \
in-interface-list=WAN
add action=accept chain=input comment=TP10-seal-est connection-state=\
established,related,untracked
add action=drop chain=input comment=TP10-seal-inv connection-state=invalid
add action=accept chain=input comment="TP10: WG hub listen (51821)" dst-port=\
51821 protocol=udp
add action=accept chain=forward comment="TP10: allow from WG spokes (FM/DW)" \
in-interface=wg-hub
add action=accept chain=forward comment="TP10: allow to WG spokes (FM/DW)" \
out-interface=wg-hub
add action=accept chain=input comment=TP10-seal-dhcp dst-port=68 \
in-interface-list=WAN protocol=udp
add action=accept chain=input comment=TP10-seal-icmp protocol=icmp
add action=accept chain=input comment=TP10-seal-lan in-interface-list=LAN
add action=drop chain=input comment=TP10-seal-wandrop in-interface-list=WAN
add action=accept chain=forward comment=\
"TP5: MAIN->MEDIA forward (AirPlay/Sonos)" in-interface=vlan10 \
out-interface=vlan40
add action=accept chain=forward comment=\
"TP5: MEDIA->MAIN forward (AirPlay/Sonos)" in-interface=vlan40 \
out-interface=vlan10
add action=accept chain=input comment=\
"TP5: mDNS 5353 input vlan10 (repeater)" dst-port=5353 in-interface=\
vlan10 protocol=udp
add action=accept chain=input comment=\
"TP5: mDNS 5353 input vlan40 (repeater)" dst-port=5353 in-interface=\
vlan40 protocol=udp
add action=accept chain=forward comment=\
"TP5: INFRA->MEDIA forward (HA<->Sonos)" in-interface=vlan20 \
out-interface=vlan40
add action=accept chain=forward comment=\
"TP5: MEDIA->INFRA forward (HA<->Sonos)" in-interface=vlan40 \
out-interface=vlan20
add action=accept chain=input comment=\
"TP5: mDNS 5353 input vlan20 (repeater/HA)" dst-port=5353 in-interface=\
vlan20 protocol=udp
add action=accept chain=input comment=\
"TP5: mDNS 5353 input vlan30 (repeater)" dst-port=5353 in-interface=\
vlan30 protocol=udp
add action=accept chain=input comment=\
"TP5: mDNS 5353 input vlan90 (repeater)" dst-port=5353 in-interface=\
vlan90 protocol=udp
add action=accept chain=forward comment=\
"TP5: GUEST->MEDIA forward (Sonos/Cast)" in-interface=vlan90 \
out-interface=vlan40
add action=accept chain=forward comment=\
"TP5: MEDIA->GUEST forward (Sonos/Cast)" in-interface=vlan40 \
out-interface=vlan90
/ip firewall nat
add action=masquerade chain=srcnat comment="defconf: masquerade" \
ipsec-policy=out,none out-interface-list=WAN
add action=accept chain=srcnat comment=\
"TP4: no-NAT inter-VLAN (LAN-internal, before masquerade)" dst-address=\
10.40.0.0/16
add action=masquerade chain=srcnat comment="TP4: masquerade VLANs toward FB" \
disabled=yes out-interface=bridge
add action=masquerade chain=srcnat comment=\
"TP4: masquerade VLANs via ether1 (Liwest WAN)" disabled=yes \
out-interface=ether1
/ip route
add comment="TP10: FM spoke -> wg-hub" dst-address=10.101.0.0/24 gateway=\
wg-hub
add comment="TP10: DW spoke (kuenftig) -> wg-hub" dst-address=10.95.10.0/24 \
gateway=wg-hub
/ipv6 firewall address-list
add address=::/128 comment="defconf: unspecified address" list=bad_ipv6
add address=::1/128 comment="defconf: lo" list=bad_ipv6
add address=fec0::/10 comment="defconf: site-local" list=bad_ipv6
add address=::ffff:0.0.0.0/96 comment="defconf: ipv4-mapped" list=bad_ipv6
add address=::/96 comment="defconf: ipv4 compat" list=bad_ipv6
add address=100::/64 comment="defconf: discard only " list=bad_ipv6
add address=2001:db8::/32 comment="defconf: documentation" list=bad_ipv6
add address=2001:10::/28 comment="defconf: ORCHID" list=bad_ipv6
add address=3ffe::/16 comment="defconf: 6bone" list=bad_ipv6
/ipv6 firewall filter
add action=accept chain=input comment=\
"defconf: accept established,related,untracked" connection-state=\
established,related,untracked
add action=drop chain=input comment="defconf: drop invalid" connection-state=\
invalid
add action=accept chain=input comment="defconf: accept ICMPv6" protocol=\
icmpv6
add action=accept chain=input comment="defconf: accept UDP traceroute" \
dst-port=33434-33534 protocol=udp
add action=accept chain=input comment=\
"defconf: accept DHCPv6-Client prefix delegation." dst-port=546 protocol=\
udp src-address=fe80::/10
add action=accept chain=input comment="defconf: accept IKE" dst-port=500,4500 \
protocol=udp
add action=accept chain=input comment="defconf: accept ipsec AH" protocol=\
ipsec-ah
add action=accept chain=input comment="defconf: accept ipsec ESP" protocol=\
ipsec-esp
add action=accept chain=input comment=\
"defconf: accept all that matches ipsec policy" ipsec-policy=in,ipsec
add action=drop chain=input comment=\
"defconf: drop everything else not coming from LAN" in-interface-list=\
!LAN
add action=fasttrack-connection chain=forward comment="defconf: fasttrack6" \
connection-state=established,related
add action=accept chain=forward comment=\
"defconf: accept established,related,untracked" connection-state=\
established,related,untracked
add action=drop chain=forward comment="defconf: drop invalid" \
connection-state=invalid
add action=drop chain=forward comment=\
"defconf: drop packets with bad src ipv6" src-address-list=bad_ipv6
add action=drop chain=forward comment=\
"defconf: drop packets with bad dst ipv6" dst-address-list=bad_ipv6
add action=drop chain=forward comment="defconf: rfc4890 drop hop-limit=1" \
hop-limit=equal:1 protocol=icmpv6
add action=accept chain=forward comment="defconf: accept ICMPv6" protocol=\
icmpv6
add action=accept chain=forward comment="defconf: accept HIP" protocol=139
add action=accept chain=forward comment="defconf: accept IKE" dst-port=\
500,4500 protocol=udp
add action=accept chain=forward comment="defconf: accept ipsec AH" protocol=\
ipsec-ah
add action=accept chain=forward comment="defconf: accept ipsec ESP" protocol=\
ipsec-esp
add action=accept chain=forward comment=\
"defconf: accept all that matches ipsec policy" ipsec-policy=in,ipsec
add action=drop chain=forward comment=\
"defconf: drop everything else not coming from LAN" in-interface-list=\
!LAN
/system clock
set time-zone-name=Europe/Vienna
/system identity
set name=ap1
/system logging
add action=wantest topics=script

View File

@@ -0,0 +1,558 @@
# 2026-08-06 09:36:00 by RouterOS 7.23.2
# software id = 2ATI-ZRRH
#
# model = C52iG-5HaxD2HaxD
# serial number = HM70BFZBYVQ
/interface bridge
add admin-mac=D0:EA:11:4D:40:E1 auto-mac=no comment=defconf name=bridge \
vlan-filtering=yes
/interface wireguard
add comment="TP10 Multi-Site VPN Hub (FM/DW spokes). Kein IP aufs IF (Routing \
via AllowedIPs/Routes)." listen-port=51821 mtu=1412 name=wg-hub
/interface vlan
add comment="MAIN 10.40.10.0/24" interface=bridge name=vlan10 vlan-id=10
add comment="INFRA 10.40.20.0/24" interface=bridge name=vlan20 vlan-id=20
add comment="IOT 10.40.30.0/24" interface=bridge name=vlan30 vlan-id=30
add comment="MEDIA 10.40.40.0/24" interface=bridge name=vlan40 vlan-id=40
add comment="UNTRUSTED 10.40.50.0/24" interface=bridge name=vlan50 vlan-id=50
add comment="GUEST 10.40.90.0/24" interface=bridge name=vlan90 vlan-id=90
add comment="MGMT 10.40.99.0/24" interface=bridge name=vlan99 vlan-id=99
add comment="WAN Arris (Liwest)" interface=bridge name=vlan100 vlan-id=100
/interface list
add comment=defconf name=WAN
add comment=defconf name=LAN
/interface wifi channel
add band=2ghz-ax disabled=no frequency=2462 name=ch-2g width=20mhz
add band=5ghz-ax disabled=no frequency=5745 name=ch-5g skip-dfs-channels=\
10min-cac width=20/40/80mhz
/interface wifi datapath
add bridge=bridge name=dp-main vlan-id=10
add bridge=bridge name=dp-iot vlan-id=30
add bridge=bridge name=dp-media vlan-id=40
add bridge=bridge name=dp-untrusted vlan-id=50
add bridge=bridge name=dp-guest vlan-id=90
add bridge=bridge name=dp-infra vlan-id=20
/interface wifi security
add authentication-types=wpa3-psk disabled=no ft=yes ft-over-ds=yes name=\
sec-main
add authentication-types=wpa2-psk,wpa3-psk disabled=no ft=no name=sec-iot
add authentication-types=wpa2-psk,wpa3-psk disabled=no ft=no ft-over-ds=no \
name=sec-media
add authentication-types=wpa2-psk,wpa3-psk disabled=no ft=no name=\
sec-untrusted
add authentication-types=wpa2-psk,wpa3-psk ft=yes ft-over-ds=yes name=\
sec-guest
add authentication-types=wpa2-psk,wpa3-psk ft=no name=sec-infra
/interface wifi configuration
add country=Austria disabled=no mode=ap name=cfg-main-2g security=sec-main \
ssid=dirtyvader-main tx-power=20
add country=Austria datapath=dp-iot mode=ap multicast-enhance=enabled name=\
cfg-iot security=sec-iot ssid=dirtyvader-iot
add country=Austria datapath=dp-media mode=ap multicast-enhance=enabled name=\
cfg-media security=sec-media ssid=dirtyvader-media
add country=Austria datapath=dp-untrusted mode=ap name=cfg-untrusted \
security=sec-untrusted ssid=dirtyvader-untrusted
add country=Austria datapath=dp-guest mode=ap name=cfg-guest security=\
sec-guest ssid=dirtyvader-guest
add country=Austria datapath=dp-infra mode=ap name=cfg-infra security=\
sec-infra ssid=dirtyvader-infra
/interface wifi
set [ find default-name=wifi2 ] channel.frequency=2412 configuration=\
cfg-main-2g configuration.manager=local disabled=no
add configuration=cfg-guest disabled=no mac-address=D2:EA:11:4D:40:EB \
master-interface=wifi2 name=w2-guest
add configuration=cfg-iot disabled=no mac-address=D2:EA:11:4D:40:E8 \
master-interface=wifi2 name=w2-iot
add configuration=cfg-media disabled=no mac-address=D2:EA:11:4D:40:E9 \
master-interface=wifi2 name=w2-media
add configuration=cfg-untrusted disabled=no mac-address=D2:EA:11:4D:40:EA \
master-interface=wifi2 name=w2-untrusted
/interface wifi steering
add name=st-main neighbor-group=bf-main rrm=yes wnm=yes
/interface wifi configuration
add channel=ch-5g country=Austria datapath=dp-main mode=ap name=cfg-main-5g \
security=sec-main ssid=dirtyvader-main steering=st-main
/interface wifi
set [ find default-name=wifi1 ] channel.frequency=5180 configuration=\
cfg-main-5g configuration.manager=local disabled=no
add configuration=cfg-guest disabled=no mac-address=D2:EA:11:4D:40:E7 \
master-interface=wifi1 name=w5-guest
add configuration=cfg-media disabled=no mac-address=D2:EA:11:4D:40:E5 \
master-interface=wifi1 name=w5-media
add configuration=cfg-untrusted disabled=no mac-address=D2:EA:11:4D:40:E6 \
master-interface=wifi1 name=w5-untrusted
/ip dhcp-server option
add code=6 name="dns 1.1.1.1" value=0x01010101
add code=138 name=omada-inform value=0x0A2814AF
/ip pool
add name=default-dhcp ranges=192.168.88.10-192.168.88.254
add name=pool-v10 ranges=10.40.10.100-10.40.10.200
add name=pool-v20 ranges=10.40.20.100-10.40.20.200
add name=pool-v30 ranges=10.40.30.100-10.40.30.250
add name=pool-v40 ranges=10.40.40.100-10.40.40.200
add name=pool-v50 ranges=10.40.50.100-10.40.50.200
add name=pool-v90 ranges=10.40.90.100-10.40.90.200
add name=pool-v99 ranges=10.40.99.100-10.40.99.200
add comment="BF flat .41 (legacy, retiring)" name=pool-v1 ranges=\
10.40.41.180-10.40.41.199
/ip dhcp-server
add address-pool=pool-v1 disabled=yes interface=bridge lease-time=2h name=\
defconf
add address-pool=pool-v10 comment=MAIN interface=vlan10 lease-time=1d name=\
dhcp-v10
add address-pool=pool-v20 comment=INFRA interface=vlan20 lease-time=1d name=\
dhcp-v20
add address-pool=pool-v30 comment=IOT interface=vlan30 lease-time=1d name=\
dhcp-v30
add address-pool=pool-v40 comment=MEDIA interface=vlan40 lease-time=1d name=\
dhcp-v40
add address-pool=pool-v50 comment=UNTRUSTED interface=vlan50 lease-time=1d \
name=dhcp-v50
add address-pool=pool-v90 comment=GUEST interface=vlan90 lease-time=12h name=\
dhcp-v90
add address-pool=pool-v99 comment=MGMT interface=vlan99 lease-time=1d name=\
dhcp-v99
/system logging action
add disk-file-name=wan-test disk-lines-per-file=2000 name=wantest target=disk
/system script
add dont-require-permissions=no name=wantest-probe owner=admin policy=\
read,write,policy,test source=" :local ts ([/system clock get date] . \" \
\" . [/system clock get time])\
\n :local e1stat \"\?\"\
\n :do { :set e1stat [/ip dhcp-client get [find interface=ether1] status]\
\_} on-error={}\
\n :local e1addr \"-\"\
\n :do { :set e1addr [/ip dhcp-client get [find interface=ether1] address\
] } on-error={}\
\n :local e1mac [/interface ethernet get [find name=ether1] mac-address]\
\n :log info (\"WANTEST [\" . \$ts . \"] ether1 status=\" . \$e1stat . \"\
\_addr=\" . \$e1addr . \" mac=\" . \$e1mac)\
\n :local gw \"-\"\
\n :do { :set gw [/ip route get [find dst-address=\"0.0.0.0/0\" active=ye\
s] gateway] } on-error={}\
\n :log info (\"WANTEST route default-gw=\" . \$gw)\
\n # WAN-Routing+NAT: Router -> public 1.1.1.1 (UDP/53). UNABH\C3\84NGIG \
von ap1-DNS-Config.\
\n # (ICMP/HTTPS sind hier geblockt -> :resolve gegen feste IP ist das ro\
buste Signal.)\
\n :do { :local w [:resolve \"google.com\" server=1.1.1.1] ; :log info (\
\"WANTEST wan-reach via-1.1.1.1 ok google.com=\" . \$w) } on-error={ :log \
info \"WANTEST wan-reach via-1.1.1.1 FAIL (Routing/NAT\?)\" }\
\n # DoH end-to-end: ap1-Resolver = was die Clients als DNS bekommen\
\n :do { :local d [:resolve \"google.com\"] ; :log info (\"WANTEST dns-do\
h ok google.com=\" . \$d) } on-error={ :log info \"WANTEST dns-doh FAIL (D\
oH/Bootstrap\?)\" }"
add dont-require-permissions=no name=wantest-run owner=admin policy=\
read,write,policy,test source=" :global WTN\
\n :if (([:typeof \$WTN] = \"nothing\") or (\$WTN = \"\")) do={ :set WTN \
24 }\
\n :log info (\"WANTEST run start (\" . \$WTN . \"x @15s, ~\" . (\$WTN * \
15 / 60) . \" min)\")\
\n :for i from=1 to=\$WTN do={\
\n /system script run wantest-probe\
\n :delay 15s\
\n }\
\n :log info \"WANTEST run done\""
add dont-require-permissions=no name=wanmac-clone owner=admin policy=\
read,write,policy,test source=" :global FBMAC\
\n :if (([:typeof \$FBMAC] = \"nothing\") or (\$FBMAC = \"\")) do={ :set \
FBMAC \"0C:72:74:36:90:99\" }\
\n :global E1ORIG\
\n :set E1ORIG [/interface ethernet get [find name=ether1] mac-address]\
\n :log info (\"WANTEST macclone: orig=\" . \$E1ORIG . \" -> fb=\" . \$FB\
MAC . \" (Auto-Revert in ~90s)\")\
\n /interface ethernet set [find name=ether1] mac-address=\$FBMAC\
\n :do { /ip dhcp-client release [find interface=ether1] } on-error={}\
\n :delay 2s\
\n :do { /ip dhcp-client renew [find interface=ether1] } on-error={}\
\n :delay 25s\
\n :for i from=1 to=4 do={ /system script run wantest-probe ; :delay 15s \
}\
\n # --- garantierter Auto-Revert im selben Lauf (kein Scheduler n\C3\B6t\
ig) ---\
\n /interface ethernet reset-mac-address [find name=ether1]\
\n :do { /ip dhcp-client release [find interface=ether1] } on-error={}\
\n :delay 2s\
\n :do { /ip dhcp-client renew [find interface=ether1] } on-error={}\
\n :delay 15s\
\n /system script run wantest-probe\
\n :log info (\"WANTEST macclone done -> revert auf factory-MAC (orig war\
\_\" . \$E1ORIG . \")\")"
add dont-require-permissions=no name=wanmac-revert owner=admin policy=\
read,write,policy,test source=" /interface ethernet reset-mac-address [fi\
nd name=ether1]\
\n :do { /ip dhcp-client release [find interface=ether1] } on-error={}\
\n :delay 2s\
\n :do { /ip dhcp-client renew [find interface=ether1] } on-error={}\
\n :delay 10s\
\n /system script run wantest-probe\
\n :log info \"WANTEST macclone manual-revert -> factory-MAC\""
/disk settings
set auto-media-interface=bridge auto-media-sharing=yes auto-smb-sharing=yes
/interface bridge port
add bridge=bridge comment=defconf interface=ether2
add bridge=bridge comment=defconf interface=ether3
add bridge=bridge comment=defconf interface=ether4
add bridge=bridge comment=defconf interface=ether5
add bridge=bridge comment=defconf interface=wifi2
add bridge=bridge interface=ether1
/interface bridge vlan
add bridge=bridge untagged=bridge,ether2 vlan-ids=1
add bridge=bridge tagged=bridge,ether2,ether1 vlan-ids=10
add bridge=bridge tagged=bridge,ether2,ether1 vlan-ids=20
add bridge=bridge tagged=bridge,ether2,ether1 vlan-ids=30
add bridge=bridge tagged=bridge,ether2,ether1 vlan-ids=40
add bridge=bridge tagged=bridge,ether2,ether1 vlan-ids=50
add bridge=bridge tagged=bridge,ether2,ether1 vlan-ids=90
add bridge=bridge tagged=bridge,ether2,ether1 vlan-ids=99
add bridge=bridge tagged=bridge,ether2,ether1 vlan-ids=100
/interface list member
add comment=defconf interface=bridge list=LAN
add comment=defconf interface=ether1 list=WAN
add comment="WAN Arris via vlan100" interface=vlan100 list=WAN
/interface wifi access-list
add action=accept comment="pin P750W to ap1 (1/2): accept on ap1 iot" \
disabled=yes interface=w2-iot mac-address=4C:EB:BD:E3:25:F2
add action=reject comment=\
"pin P750W to ap1 (2/2): reject on any other AP incl dynamic ap2 VAPs" \
disabled=yes mac-address=4C:EB:BD:E3:25:F2
/interface wifi cap
set caps-man-addresses=10.40.41.1 discovery-interfaces=bridge
/interface wifi capsman
set enabled=yes interfaces=bridge
/interface wifi provisioning
add action=create-dynamic-enabled master-configuration=cfg-main-2g \
slave-configurations=cfg-iot,cfg-media,cfg-untrusted,cfg-guest,cfg-infra \
supported-bands=2ghz-ax
add action=create-dynamic-enabled master-configuration=cfg-main-5g \
slave-configurations=cfg-media,cfg-untrusted,cfg-guest,cfg-infra \
supported-bands=5ghz-ax
/interface wireguard peers
add allowed-address=10.101.0.0/24 comment=fm endpoint-address=\
abxhutfgsid9snmy.myfritz.net endpoint-port=52476 interface=wg-hub name=fm \
public-key="dzRY0lnnUygijChWe5wXZmXPeNcbdALpqBptA3fyiVE="
add allowed-address=10.95.10.0/24 comment=dw interface=wg-hub name=dw \
public-key="FqYZLztQ/qME5VDie5QLGH++5299WJiE1dPpQnPbzUw="
/ip address
add address=10.40.41.1/24 comment=defconf interface=bridge network=10.40.41.0
add address=10.40.10.1/24 comment="MAIN GW" interface=vlan10 network=\
10.40.10.0
add address=10.40.20.1/24 comment="INFRA GW" interface=vlan20 network=\
10.40.20.0
add address=10.40.30.1/24 comment="IOT GW" interface=vlan30 network=\
10.40.30.0
add address=10.40.40.1/24 comment="MEDIA GW" interface=vlan40 network=\
10.40.40.0
add address=10.40.50.1/24 comment="UNTRUSTED GW" interface=vlan50 network=\
10.40.50.0
add address=10.40.90.1/24 comment="GUEST GW" interface=vlan90 network=\
10.40.90.0
add address=10.40.99.1/24 comment="MGMT GW" interface=vlan99 network=\
10.40.99.0
add address=10.40.41.254/24 comment="Secondary = alte FB-GW-IP; haelt statisch\
e .41-Hosts online nach Arris-Swap (ap1 NATs + WG-Hub)" interface=bridge \
network=10.40.41.0
/ip dhcp-client
add comment=defconf disabled=yes interface=ether1 name=client1 use-peer-dns=\
no
add comment="WAN Arris DHCP (Liwest) via vlan100" interface=vlan100 name=\
client2 use-peer-dns=no use-peer-ntp=no
/ip dhcp-server lease
add address=10.40.10.82 client-id=1:56:71:6c:22:e6:a9 dhcp-option=\
"dns 1.1.1.1" mac-address=56:71:6C:22:E6:A9 server=dhcp-v10
add address=10.40.10.196 client-id=1:f2:62:4c:e1:af:9 mac-address=\
F2:62:4C:E1:AF:09 server=dhcp-v10
add address=10.40.40.199 client-id=1:48:a6:b8:b:63:10 mac-address=\
48:A6:B8:0B:63:10 server=dhcp-v40
add address=10.40.40.191 client-id=1:34:7e:5c:32:37:96 mac-address=\
34:7E:5C:32:37:96 server=dhcp-v40
add address=10.40.40.192 client-id=1:34:7e:5c:32:33:b8 mac-address=\
34:7E:5C:32:33:B8 server=dhcp-v40
add address=10.40.40.193 client-id=1:78:28:ca:80:2a:f6 mac-address=\
78:28:CA:80:2A:F6 server=dhcp-v40
add address=10.40.40.195 client-id=1:78:28:ca:82:16:c2 mac-address=\
78:28:CA:82:16:C2 server=dhcp-v40
add address=10.40.40.201 client-id=1:94:9f:3e:49:59:ee mac-address=\
94:9F:3E:49:59:EE server=dhcp-v40
add address=10.40.40.196 client-id=1:48:a6:b8:16:38:ae mac-address=\
48:A6:B8:16:38:AE server=dhcp-v40
add address=10.40.40.197 client-id=1:48:a6:b8:14:fe:f2 mac-address=\
48:A6:B8:14:FE:F2 server=dhcp-v40
add address=10.40.40.200 client-id=1:48:a6:b8:69:69:7b mac-address=\
48:A6:B8:69:69:7B server=dhcp-v40
add address=10.40.40.194 client-id=1:48:a6:b8:14:fe:d0 mac-address=\
48:A6:B8:14:FE:D0 server=dhcp-v40
add address=10.40.40.198 comment=SonosTerrasse mac-address=78:28:CA:D2:0D:AC \
server=dhcp-v40
add address=10.40.30.202 comment=shelly-s22_kueche_steckdose3-2 mac-address=\
A0:85:E3:C8:65:34 server=dhcp-v30
add address=10.40.30.203 comment=shelly-s23_dieleug-wohnzimmer_licht \
mac-address=AC:15:18:7B:62:20 server=dhcp-v30
add address=10.40.30.205 comment=shelly-s25_bad_lichtschalter mac-address=\
98:A3:16:79:38:50 server=dhcp-v30
add address=10.40.30.206 comment=shelly-s20_kueche_steckdosen2 mac-address=\
98:A3:16:7C:5F:EC server=dhcp-v30
add address=10.40.30.207 comment=shelly-s21_kueche_steckdosen4 mac-address=\
20:6E:F1:01:F9:F8 server=dhcp-v30
add address=10.40.30.208 comment=shelly-s1_weihnachten_kueche mac-address=\
E4:B0:63:D7:C7:A4 server=dhcp-v30
add address=10.40.30.209 comment=shelly-s2_weihnachten_terrasse mac-address=\
28:37:2F:36:F4:7C server=dhcp-v30
add address=10.40.30.210 comment=shelly-s3_weihnachten_kinderzimmer \
mac-address=E4:B0:63:E4:EF:7C server=dhcp-v30
add address=10.40.30.211 comment=shelly-s4_kueche_mikrowelle mac-address=\
E4:B0:63:E4:0F:30 server=dhcp-v30
add address=10.40.30.212 comment=shelly-s5_kueche_backofen mac-address=\
D8:85:AC:0C:5C:C8 server=dhcp-v30
add address=10.40.30.213 comment=shelly-s6_kueche_kuehlschrank mac-address=\
D0:CF:13:D8:18:FC server=dhcp-v30
add address=10.40.30.214 comment=shelly-s7_kueche_tiefkuehlschrank \
mac-address=D0:CF:13:D9:94:E8 server=dhcp-v30
add address=10.40.30.215 comment=shelly-s8_kueche_geschirrspueler \
mac-address=E4:B0:63:FA:FD:E0 server=dhcp-v30
add address=10.40.30.216 comment=shelly-s9_wohnzimmer_sideboard mac-address=\
E8:F6:0A:61:88:08 server=dhcp-v30
add address=10.40.30.217 comment=shelly-s10_bad_sonos mac-address=\
E4:B0:63:FC:57:E4 server=dhcp-v30
add address=10.40.30.218 comment=shelly-s11_kinderzimmer_sonos mac-address=\
E4:B0:63:FA:6C:00 server=dhcp-v30
add address=10.40.30.219 comment=shelly-s12_dieleug_steckdosen mac-address=\
48:F6:EE:C8:E6:AC server=dhcp-v30
add address=10.40.30.220 comment=shelly-s13_arbeitszimmer_technik \
mac-address=48:F6:EE:DD:6A:F4 server=dhcp-v30
add address=10.40.30.221 comment=shelly-s14_schlafzimmer_bett mac-address=\
E4:B0:63:FC:59:48 server=dhcp-v30
add address=10.40.30.222 comment=shelly-s15_technik_pve mac-address=\
E4:B0:63:FA:D5:34 server=dhcp-v30
add address=10.40.30.223 comment=shelly-s16_wohnzimmer_steckdosen \
mac-address=10:20:BA:FA:F5:40 server=dhcp-v30
add address=10.40.30.224 comment=shelly-s17_bad_steckdosen mac-address=\
10:20:BA:FB:6D:64 server=dhcp-v30
add address=10.40.30.225 comment=shelly-s18_kueche_steckdosen1 mac-address=\
48:F6:EE:CC:16:E4 server=dhcp-v30
add address=10.40.30.226 comment=shelly-s19_kueche_steckdosen3 mac-address=\
10:20:BA:FB:70:FC server=dhcp-v30
add address=10.40.30.227 comment=switchbot-floorlamp mac-address=\
B8:F8:62:09:F2:04 server=dhcp-v30
add address=10.40.30.228 comment=Twinkly_Weihnachtsgirlande mac-address=\
98:F4:AB:2C:A7:88 server=dhcp-v30
add address=10.40.30.230 comment=Withings_waage mac-address=00:24:E4:40:2A:72 \
server=dhcp-v30
add address=10.40.30.231 comment=Withings_blutdruck mac-address=\
A4:7E:FA:18:2C:64 server=dhcp-v30
add address=10.40.30.232 comment=pve-homematic-raspi-ccu mac-address=\
BC:24:11:8E:0A:57 server=dhcp-v30
add address=10.40.30.233 comment=shelly-s26_bad_luefter mac-address=\
98:A3:16:7A:A3:54 server=dhcp-v30
add address=10.40.30.236 comment=lichterkette mac-address=A4:F0:0F:5F:D0:EC \
server=dhcp-v30
add address=10.40.30.237 comment=lichterkette-mini mac-address=\
24:DC:C3:9B:89:64 server=dhcp-v30
add address=10.40.30.241 comment=NukiSmartlock mac-address=28:DB:A7:09:7B:54 \
server=dhcp-v30
add address=10.40.30.244 comment=NukiBridge mac-address=24:62:AB:B1:63:F4 \
server=dhcp-v30
add address=10.40.30.8 comment=DTUBI-414392337823 mac-address=\
54:F2:9F:12:51:F0 server=dhcp-v30
add address=10.40.20.10 comment=nas-bf mac-address=90:09:D0:58:20:F9 server=\
dhcp-v20
add address=10.40.40.11 comment=tv mac-address=70:AF:24:B8:C9:86 server=\
dhcp-v40
add address=10.40.30.5 comment=P750W mac-address=4C:EB:BD:E3:25:F2 server=\
dhcp-v30
add address=10.40.30.180 client-id=1:e4:bc:96:1f:b2:81 comment=Airfryer \
dhcp-option="dns 1.1.1.1" mac-address=E4:BC:96:1F:B2:81 server=dhcp-v30
add address=10.40.30.204 comment=shelly-s24_bad_spiegelschrank mac-address=\
14:33:5C:D8:2D:7C server=dhcp-v30
add address=10.40.30.150 client-id=1:fc:ee:11:3:32:26 comment=Ender3V3KE-3226 \
mac-address=FC:EE:11:03:32:26 server=dhcp-v30
/ip dhcp-server network
add address=10.40.10.0/24 comment=MAIN dns-server=10.40.10.1 gateway=\
10.40.10.1
add address=10.40.20.0/24 comment=INFRA dns-server=10.40.20.1 gateway=\
10.40.20.1
add address=10.40.30.0/24 comment=IOT dns-server=10.40.30.1 gateway=\
10.40.30.1
add address=10.40.40.0/24 comment=MEDIA dns-server=10.40.40.1 gateway=\
10.40.40.1
add address=10.40.41.0/24 comment=defconf dns-server=10.40.41.1 gateway=\
10.40.41.1 netmask=24
add address=10.40.50.0/24 comment=UNTRUSTED dns-server=10.40.50.1 gateway=\
10.40.50.1
add address=10.40.90.0/24 comment=GUEST dns-server=10.40.90.1 gateway=\
10.40.90.1
add address=10.40.99.0/24 comment=MGMT dhcp-option=omada-inform dns-server=\
10.40.99.1 gateway=10.40.99.1
/ip dns
set allow-remote-requests=yes mdns-repeat-ifaces=\
vlan10,vlan20,vlan40,vlan30,vlan90,bridge servers=1.1.1.1 use-doh-server=\
https://px35ynlol3.cloudflare-gateway.com/dns-query verify-doh-cert=yes
/ip dns static
add address=10.40.41.1 comment=defconf name=router.lan type=A
add address=10.40.41.2 comment=\
"TP4-transition; update to 10.40.99.2 after MGMT migration" name=\
ap2.bf.lan type=A
add address=10.40.41.11 comment="ALLNET core switch" name=sw1.bf.lan type=A
add address=10.40.41.12 comment="Omada access switch" name=sw2.bf.lan type=A
add address=10.40.20.167 comment=\
"Mac mini (INFRA VLAN20, migriert 2026-06-16)" name=macmini.bf.lan type=A
add address=10.40.20.100 comment="Proxmox hypervisor" name=pve.bf.lan type=A
add address=10.40.20.173 comment="pve-mcp SSE server" name=pve-mcp.bf.lan \
type=A
add address=10.40.20.10 comment=\
"DS923+ NAS (VLAN20, TP14.1, DNS-Sweep TP14.3 2026-07-16)" name=\
nas.bf.lan type=A
add address=10.40.20.126 comment=\
"Docker-LXC / Portainer (VLAN20, TP14.2, DNS-Sweep TP14.3 2026-07-16)" \
name=docker.bf.lan type=A
add address=10.40.99.1 comment=\
"TP19.1: ap1 MGMT = vlan99 GW (live since TP4), no separate move needed" \
name=ap1.bf.lan type=A
add address=10.40.20.20 comment=\
"TP20: Platform-Postgres LXC 105 on pve, PG16, nautobot DB" name=\
platform-pg.bf.lan type=A
/ip firewall filter
add action=accept chain=forward comment="defconf: accept in ipsec policy" \
ipsec-policy=in,ipsec
add action=accept chain=forward comment="defconf: accept out ipsec policy" \
ipsec-policy=out,ipsec
add action=fasttrack-connection chain=forward comment="defconf: fasttrack" \
connection-state=established,related
add action=accept chain=forward comment=\
"defconf: accept established,related, untracked" connection-state=\
established,related,untracked
add action=drop chain=forward comment="defconf: drop invalid" \
connection-state=invalid
add action=drop chain=forward comment=\
"defconf: drop all from WAN not DSTNATed" connection-nat-state=!dstnat \
in-interface-list=WAN
add action=accept chain=input comment=TP10-seal-est connection-state=\
established,related,untracked
add action=drop chain=input comment=TP10-seal-inv connection-state=invalid
add action=accept chain=input comment="TP10: WG hub listen (51821)" dst-port=\
51821 protocol=udp
add action=accept chain=forward comment="TP10: allow from WG spokes (FM/DW)" \
in-interface=wg-hub
add action=accept chain=forward comment="TP10: allow to WG spokes (FM/DW)" \
out-interface=wg-hub
add action=accept chain=input comment=TP10-seal-dhcp dst-port=68 \
in-interface-list=WAN protocol=udp
add action=accept chain=input comment=TP10-seal-icmp protocol=icmp
add action=accept chain=input comment=TP10-seal-lan in-interface-list=LAN
add action=drop chain=input comment=TP10-seal-wandrop in-interface-list=WAN
add action=accept chain=forward comment=\
"TP5: MAIN->MEDIA forward (AirPlay/Sonos)" in-interface=vlan10 \
out-interface=vlan40
add action=accept chain=forward comment=\
"TP5: MEDIA->MAIN forward (AirPlay/Sonos)" in-interface=vlan40 \
out-interface=vlan10
add action=accept chain=input comment=\
"TP5: mDNS 5353 input vlan10 (repeater)" dst-port=5353 in-interface=\
vlan10 protocol=udp
add action=accept chain=input comment=\
"TP5: mDNS 5353 input vlan40 (repeater)" dst-port=5353 in-interface=\
vlan40 protocol=udp
add action=accept chain=forward comment=\
"TP5: INFRA->MEDIA forward (HA<->Sonos)" in-interface=vlan20 \
out-interface=vlan40
add action=accept chain=forward comment=\
"TP5: MEDIA->INFRA forward (HA<->Sonos)" in-interface=vlan40 \
out-interface=vlan20
add action=accept chain=input comment=\
"TP5: mDNS 5353 input vlan20 (repeater/HA)" dst-port=5353 in-interface=\
vlan20 protocol=udp
add action=accept chain=input comment=\
"TP5: mDNS 5353 input vlan30 (repeater)" dst-port=5353 in-interface=\
vlan30 protocol=udp
add action=accept chain=input comment=\
"TP5: mDNS 5353 input vlan90 (repeater)" dst-port=5353 in-interface=\
vlan90 protocol=udp
add action=accept chain=forward comment=\
"TP5: GUEST->MEDIA forward (Sonos/Cast)" in-interface=vlan90 \
out-interface=vlan40
add action=accept chain=forward comment=\
"TP5: MEDIA->GUEST forward (Sonos/Cast)" in-interface=vlan40 \
out-interface=vlan90
/ip firewall nat
add action=masquerade chain=srcnat comment="defconf: masquerade" \
ipsec-policy=out,none out-interface-list=WAN
add action=accept chain=srcnat comment=\
"TP4: no-NAT inter-VLAN (LAN-internal, before masquerade)" dst-address=\
10.40.0.0/16
add action=masquerade chain=srcnat comment="TP4: masquerade VLANs toward FB" \
disabled=yes out-interface=bridge
add action=masquerade chain=srcnat comment=\
"TP4: masquerade VLANs via ether1 (Liwest WAN)" disabled=yes \
out-interface=ether1
/ip route
add comment="TP10: FM spoke -> wg-hub" dst-address=10.101.0.0/24 gateway=\
wg-hub
add comment="TP10: DW spoke (kuenftig) -> wg-hub" dst-address=10.95.10.0/24 \
gateway=wg-hub
/ipv6 firewall address-list
add address=::/128 comment="defconf: unspecified address" list=bad_ipv6
add address=::1/128 comment="defconf: lo" list=bad_ipv6
add address=fec0::/10 comment="defconf: site-local" list=bad_ipv6
add address=::ffff:0.0.0.0/96 comment="defconf: ipv4-mapped" list=bad_ipv6
add address=::/96 comment="defconf: ipv4 compat" list=bad_ipv6
add address=100::/64 comment="defconf: discard only " list=bad_ipv6
add address=2001:db8::/32 comment="defconf: documentation" list=bad_ipv6
add address=2001:10::/28 comment="defconf: ORCHID" list=bad_ipv6
add address=3ffe::/16 comment="defconf: 6bone" list=bad_ipv6
/ipv6 firewall filter
add action=accept chain=input comment=\
"defconf: accept established,related,untracked" connection-state=\
established,related,untracked
add action=drop chain=input comment="defconf: drop invalid" connection-state=\
invalid
add action=accept chain=input comment="defconf: accept ICMPv6" protocol=\
icmpv6
add action=accept chain=input comment="defconf: accept UDP traceroute" \
dst-port=33434-33534 protocol=udp
add action=accept chain=input comment=\
"defconf: accept DHCPv6-Client prefix delegation." dst-port=546 protocol=\
udp src-address=fe80::/10
add action=accept chain=input comment="defconf: accept IKE" dst-port=500,4500 \
protocol=udp
add action=accept chain=input comment="defconf: accept ipsec AH" protocol=\
ipsec-ah
add action=accept chain=input comment="defconf: accept ipsec ESP" protocol=\
ipsec-esp
add action=accept chain=input comment=\
"defconf: accept all that matches ipsec policy" ipsec-policy=in,ipsec
add action=drop chain=input comment=\
"defconf: drop everything else not coming from LAN" in-interface-list=\
!LAN
add action=fasttrack-connection chain=forward comment="defconf: fasttrack6" \
connection-state=established,related
add action=accept chain=forward comment=\
"defconf: accept established,related,untracked" connection-state=\
established,related,untracked
add action=drop chain=forward comment="defconf: drop invalid" \
connection-state=invalid
add action=drop chain=forward comment=\
"defconf: drop packets with bad src ipv6" src-address-list=bad_ipv6
add action=drop chain=forward comment=\
"defconf: drop packets with bad dst ipv6" dst-address-list=bad_ipv6
add action=drop chain=forward comment="defconf: rfc4890 drop hop-limit=1" \
hop-limit=equal:1 protocol=icmpv6
add action=accept chain=forward comment="defconf: accept ICMPv6" protocol=\
icmpv6
add action=accept chain=forward comment="defconf: accept HIP" protocol=139
add action=accept chain=forward comment="defconf: accept IKE" dst-port=\
500,4500 protocol=udp
add action=accept chain=forward comment="defconf: accept ipsec AH" protocol=\
ipsec-ah
add action=accept chain=forward comment="defconf: accept ipsec ESP" protocol=\
ipsec-esp
add action=accept chain=forward comment=\
"defconf: accept all that matches ipsec policy" ipsec-policy=in,ipsec
add action=drop chain=forward comment=\
"defconf: drop everything else not coming from LAN" in-interface-list=\
!LAN
/system clock
set time-zone-name=Europe/Vienna
/system identity
set name=ap1
/system logging
add action=wantest topics=script

View File

@@ -0,0 +1,75 @@
#!/usr/bin/env python3
"""Lade die staged L3-Cutover-Config auf crs326-core (noch NICHT enablen).
Creds aus ~/.claude.json. Alle operativen Objekte in der rsc sind disabled=yes,
also bleibt der aktive Datenpfad unberührt.
"""
import json
import pathlib
import subprocess
import sys
CORE_IP = "10.40.41.211"
RSC = pathlib.Path(__file__).with_name("tp15-core-cutover-intended.rsc")
CLAUDE_JSON = pathlib.Path.home() / ".claude.json"
def load_core_creds():
cfg = json.loads(CLAUDE_JSON.read_text())
env = cfg.get("mcpServers", {}).get("mikromcp", {}).get("env", {})
user = env.get("CRS326_CORE_USER") or env.get("ROUTER_USER")
pw = env.get("CRS326_CORE_PASS") or env.get("ROUTER_PASS")
if not user or not pw:
raise SystemExit("CRS326_CORE_USER/PASS nicht in ~/.claude.json mikromcp.env gefunden")
return user, pw
def ssh_base(user, pw):
return [
"sshpass", "-p", pw,
"ssh",
"-o", "StrictHostKeyChecking=no",
"-o", "UserKnownHostsFile=/dev/null",
"-o", "PubkeyAuthentication=no",
f"{user}@{CORE_IP}",
]
def run_silent(base, cmd):
r = subprocess.run(base + [cmd], capture_output=True, text=True)
if r.returncode != 0:
out = (r.stdout + r.stderr).replace(pw, "***")
raise SystemExit(f"Befehl fehlgeschlagen (rc={r.returncode}):\n{out}")
return r
def main():
if not RSC.exists():
raise SystemExit(f"{RSC} fehlt")
user, pw = load_core_creds()
base = ssh_base(user, pw)
print(f"crs326-core L3-Cutover-Config laden ({RSC.name}) ...")
# 1) Datei auf Router kopieren
r = subprocess.run(
["sshpass", "-p", pw,
"scp",
"-o", "StrictHostKeyChecking=no",
"-o", "UserKnownHostsFile=/dev/null",
"-o", "PubkeyAuthentication=no",
str(RSC),
f"{user}@{CORE_IP}:{RSC.name}"],
capture_output=True, text=True,
)
if r.returncode != 0:
raise SystemExit(f"SCP fehlgeschlagen: {r.stderr.replace(pw, '***')}")
# 2) Import auslösen
run_silent(base, f'/import file-name="{RSC.name}"')
print(" Import abgeschlossen (staged / disabled).")
print(" Nächster Schritt: L2-Portplan + Cutover-Enable.")
if __name__ == "__main__":
main()

91
routeros/backup-ap1.py Normal file
View File

@@ -0,0 +1,91 @@
#!/usr/bin/env python3
"""Erstelle ein Binary-Backup + einen Text-Export von ap1 vor dem TP15-Cutover.
Creds aus ~/.claude.json (mikromcp-env). Backup-Dateien landen unter routeros/.
"""
import json
import pathlib
import subprocess
import sys
from datetime import datetime, timezone
AP1_IP = "10.40.10.1"
CLAUDE_JSON = pathlib.Path.home() / ".claude.json"
OUT_DIR = pathlib.Path(__file__).resolve().parent
def load_ap1_creds():
cfg = json.loads(CLAUDE_JSON.read_text())
env = cfg.get("mcpServers", {}).get("mikromcp", {}).get("env", {})
user = env.get("ROUTER_AP1_USER") or env.get("ROUTER_USER")
pw = env.get("ROUTER_AP1_PASS") or env.get("ROUTER_PASS")
if not user or not pw:
raise SystemExit("ROUTER_AP1_USER/PASS nicht in ~/.claude.json mikromcp.env gefunden")
return user, pw
def ssh_cmd(user, pw, remote_command):
return [
"sshpass", "-p", pw,
"ssh",
"-o", "StrictHostKeyChecking=no",
"-o", "UserKnownHostsFile=/dev/null",
"-o", "PubkeyAuthentication=no",
f"{user}@{AP1_IP}",
remote_command,
]
def scp_cmd(user, pw, remote_file, local_file):
return [
"sshpass", "-p", pw,
"scp",
"-o", "StrictHostKeyChecking=no",
"-o", "UserKnownHostsFile=/dev/null",
"-o", "PubkeyAuthentication=no",
f"{user}@{AP1_IP}:{remote_file}",
str(local_file),
]
def main():
user, pw = load_ap1_creds()
ts = datetime.now(timezone.utc).strftime("%Y%m%d-%H%M%S")
backup_name = f"ap1-pre-core-cutover-{ts}"
print(f"ap1 Backup via SSH {user}@{AP1_IP}")
# 1) Binary backup + Text-Export auf ap1 erzeugen
create_cmd = ssh_cmd(
user, pw,
f'/system backup save name="{backup_name}" dont-encrypt=yes; '
f'/export file="{backup_name}"'
)
print(f" Erzeuge Backup/Export auf ap1 ...")
subprocess.run(create_cmd, check=True)
# 2) Herunterladen
local_backup = OUT_DIR / f"{backup_name}.backup"
local_export = OUT_DIR / f"{backup_name}.rsc"
print(f" Lade {backup_name}.backup herunter ...")
subprocess.run(scp_cmd(user, pw, f"{backup_name}.backup", local_backup), check=True)
print(f" Lade {backup_name}.rsc herunter ...")
subprocess.run(scp_cmd(user, pw, f"{backup_name}.rsc", local_export), check=True)
# 3) Verifizieren
backup_size = local_backup.stat().st_size
export_size = local_export.stat().st_size
print(f" OK: {local_backup.name} ({backup_size} Bytes)")
print(f" OK: {local_export.name} ({export_size} Bytes)")
if backup_size < 1000:
print(" WARN: Backup-Datei auffällig klein!")
sys.exit(1)
print("\nNächster Schritt: Cutover-Config auf crs326-core laden.")
if __name__ == "__main__":
main()

86
routeros/backup-core.py Normal file
View File

@@ -0,0 +1,86 @@
#!/usr/bin/env python3
"""Erstelle ein Binary-Backup + einen Text-Export von crs326-core vor dem TP15-Cutover.
Creds aus ~/.claude.json (mikromcp-env, CRS326_CORE_*). Backup-Dateien landen unter routeros/.
"""
import json
import pathlib
import subprocess
import sys
from datetime import datetime, timezone
CORE_IP = "10.40.41.211" # VLAN1-Temp; alternativ 10.40.99.11 sobald 99T auf Uplink
CLAUDE_JSON = pathlib.Path.home() / ".claude.json"
OUT_DIR = pathlib.Path(__file__).resolve().parent
def load_core_creds():
cfg = json.loads(CLAUDE_JSON.read_text())
env = cfg.get("mcpServers", {}).get("mikromcp", {}).get("env", {})
user = env.get("CRS326_CORE_USER") or env.get("ROUTER_USER")
pw = env.get("CRS326_CORE_PASS") or env.get("ROUTER_PASS")
if not user or not pw:
raise SystemExit("CRS326_CORE_USER/PASS nicht in ~/.claude.json mikromcp.env gefunden")
return user, pw
def ssh_cmd(user, pw, remote_command):
return [
"sshpass", "-p", pw,
"ssh",
"-o", "StrictHostKeyChecking=no",
"-o", "UserKnownHostsFile=/dev/null",
"-o", "PubkeyAuthentication=no",
f"{user}@{CORE_IP}",
remote_command,
]
def scp_cmd(user, pw, remote_file, local_file):
return [
"sshpass", "-p", pw,
"scp",
"-o", "StrictHostKeyChecking=no",
"-o", "UserKnownHostsFile=/dev/null",
"-o", "PubkeyAuthentication=no",
f"{user}@{CORE_IP}:{remote_file}",
str(local_file),
]
def main():
user, pw = load_core_creds()
ts = datetime.now(timezone.utc).strftime("%Y%m%d-%H%M%S")
backup_name = f"crs326-core-pre-cutover-{ts}"
print(f"crs326-core Backup via SSH {user}@{CORE_IP}")
create_cmd = ssh_cmd(
user, pw,
f'/system backup save name="{backup_name}" dont-encrypt=yes; '
f'/export file="{backup_name}"'
)
print(f" Erzeuge Backup/Export auf crs326-core ...")
subprocess.run(create_cmd, check=True)
local_backup = OUT_DIR / f"{backup_name}.backup"
local_export = OUT_DIR / f"{backup_name}.rsc"
print(f" Lade {backup_name}.backup herunter ...")
subprocess.run(scp_cmd(user, pw, f"{backup_name}.backup", local_backup), check=True)
print(f" Lade {backup_name}.rsc herunter ...")
subprocess.run(scp_cmd(user, pw, f"{backup_name}.rsc", local_export), check=True)
backup_size = local_backup.stat().st_size
export_size = local_export.stat().st_size
print(f" OK: {local_backup.name} ({backup_size} Bytes)")
print(f" OK: {local_export.name} ({export_size} Bytes)")
if backup_size < 1000:
print(" WARN: Backup-Datei auffällig klein!")
sys.exit(1)
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,59 @@
#!/usr/bin/env python3
"""Konvertiert ap1-leases-export.json in RouterOS-Befehle für crs326-core.
Nur dynamic=false-Leases werden übernommen, mit server-Namen 1:1 übernommen
(dhcp-v10..dhcp-v99), disabled=yes, damit sie im Cutover zusammen mit den
DHCP-Servern enabled werden.
Ausgabe: routeros/ap1-leases-to-core.rsc
"""
import json
import pathlib
import shlex
LEASES = pathlib.Path(__file__).with_name("ap1-leases-export.json")
OUT = pathlib.Path(__file__).with_name("ap1-leases-to-core.rsc")
def escape(s):
# RouterOS comment darf Leerzeichen enthalten; wir quotieren mit doppelten Anführungszeichen
return s.replace('\\', '\\\\').replace('"', '\\"')
def main():
leases = json.loads(LEASES.read_text())
static = [l for l in leases if str(l.get("dynamic")).lower() == "false"]
lines = [
"# ap1 static leases -> crs326-core (STAGED, disabled=yes)",
f"# Quelle: {LEASES.name} | {len(static)} statische Leases",
"",
]
for l in sorted(static, key=lambda x: (x.get("server", ""), x.get("address", ""))):
server = l.get("server", "")
if not server.startswith("dhcp-v"):
lines.append(f"# SKIP unbekannter server '{server}': {l.get('address')} {l.get('mac-address')}")
continue
mac = l.get("mac-address", "")
addr = l.get("address", "")
client_id = l.get("client-id", "")
hostname = l.get("host-name", "")
dhcp_option = l.get("dhcp-option", "")
comment_parts = [hostname, l.get("comment", "")]
comment = " / ".join(p for p in comment_parts if p).strip()
if not comment:
comment = f"lease {addr}"
cmd = f'/ip/dhcp-server/lease add server={server} mac-address="{mac}" address="{addr}"'
if client_id:
cmd += f' client-id="{client_id}"'
if dhcp_option:
cmd += f' dhcp-option={shlex.quote(dhcp_option)}'
cmd += f' comment="{escape(comment)}" disabled=yes'
lines.append(cmd)
lines.append("")
lines.append("# Cutover: /ip/dhcp-server/lease/enable [find comment~\"ap1 lease\"]")
OUT.write_text("\n".join(lines) + "\n")
print(f"{OUT} written with {len(static)} static lease commands")
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,92 @@
# 2025-09-04 23:25:19 by RouterOS 7.19.5
# software id = 4W04-URHT
#
# model = CRS326-24G-2S+
# serial number = HKA0AREXJFM
/interface bridge
add admin-mac=04:F4:1C:B4:17:76 auto-mac=no comment=defconf name=bridge \
vlan-filtering=yes
/interface ethernet
set [ find default-name=ether3 ] disabled=yes
set [ find default-name=ether4 ] disabled=yes
set [ find default-name=ether5 ] disabled=yes
set [ find default-name=ether6 ] disabled=yes
set [ find default-name=ether7 ] disabled=yes
set [ find default-name=ether8 ] disabled=yes
set [ find default-name=ether9 ] disabled=yes
set [ find default-name=ether10 ] disabled=yes
set [ find default-name=ether11 ] disabled=yes
set [ find default-name=ether12 ] disabled=yes
set [ find default-name=ether13 ] disabled=yes
set [ find default-name=ether14 ] disabled=yes
set [ find default-name=ether15 ] disabled=yes
set [ find default-name=ether16 ] disabled=yes
set [ find default-name=ether17 ] disabled=yes
set [ find default-name=ether18 ] disabled=yes
set [ find default-name=ether19 ] disabled=yes
set [ find default-name=ether20 ] disabled=yes
set [ find default-name=ether21 ] disabled=yes
set [ find default-name=ether22 ] disabled=yes
set [ find default-name=ether23 ] disabled=yes
set [ find default-name=ether24 ] disabled=yes
set [ find default-name=sfp-sfpplus1 ] disabled=yes
set [ find default-name=sfp-sfpplus2 ] disabled=yes
/interface vlan
add interface=bridge name=vlan99 vlan-id=99
/port
set 0 name=serial0
/interface bridge port
add bridge=bridge comment=defconf interface=ether1
add bridge=bridge comment=defconf interface=ether2
add bridge=bridge comment=defconf interface=ether3
add bridge=bridge comment=defconf interface=ether4
add bridge=bridge comment=defconf interface=ether5
add bridge=bridge comment=defconf interface=ether6
add bridge=bridge comment=defconf interface=ether7
add bridge=bridge comment=defconf interface=ether8
add bridge=bridge comment=defconf interface=ether9
add bridge=bridge comment=defconf interface=ether10
add bridge=bridge comment=defconf interface=ether11
add bridge=bridge comment=defconf interface=ether12
add bridge=bridge comment=defconf interface=ether13
add bridge=bridge comment=defconf interface=ether14
add bridge=bridge comment=defconf interface=ether15
add bridge=bridge comment=defconf interface=ether16
add bridge=bridge comment=defconf interface=ether17
add bridge=bridge comment=defconf interface=ether18
add bridge=bridge comment=defconf interface=ether19
add bridge=bridge comment=defconf interface=ether20
add bridge=bridge comment=defconf interface=ether21
add bridge=bridge comment=defconf interface=ether22
add bridge=bridge comment=defconf interface=ether23
add bridge=bridge comment=defconf interface=ether24
add bridge=bridge comment=defconf interface=sfp-sfpplus1
add bridge=bridge comment=defconf interface=sfp-sfpplus2
/interface bridge vlan
add bridge=bridge untagged=ether1,ether2 vlan-ids=1
add bridge=bridge tagged=ether1,ether2 vlan-ids=10
add bridge=bridge tagged=ether1,ether2 vlan-ids=20
add bridge=bridge tagged=ether1,ether2 vlan-ids=30
add bridge=bridge tagged=ether1,ether2 vlan-ids=40
add bridge=bridge tagged=ether1,ether2 vlan-ids=50
add bridge=bridge tagged=ether1,ether2 vlan-ids=90
add bridge=bridge tagged=ether1,ether2,bridge vlan-ids=99
add bridge=bridge tagged=ether1,ether2 vlan-ids=100
/ip address
add address=10.40.41.211/24 comment=TP15-bringup-vlan1 interface=bridge \
network=10.40.41.0
add address=10.40.99.11/24 comment=TP15-mgmt-target interface=vlan99 network=\
10.40.99.0
/ip route
add comment=TP15-bringup dst-address=0.0.0.0/0 gateway=10.40.41.1
/ip service
set ftp disabled=yes
set ssh address=10.40.0.0/16
set telnet disabled=yes
set www address=10.40.0.0/16
set api address=10.40.0.0/16
set api-ssl disabled=yes
/system identity
set name=crs326-core
/system routerboard settings
set enter-setup-on=delete-key

View File

@@ -0,0 +1,314 @@
# 2025-09-05 02:45:12 by RouterOS 7.19.5
# software id = 4W04-URHT
#
# model = CRS326-24G-2S+
# serial number = HKA0AREXJFM
/interface bridge
set [find name=bridge] admin-mac=04:F4:1C:B4:17:76 auto-mac=no vlan-filtering=yes
/interface ethernet
set [ find default-name=sfp-sfpplus1 ] disabled=yes
set [ find default-name=sfp-sfpplus2 ] disabled=yes
/interface wireguard
add comment="TP10 Multi-Site VPN Hub (FM/DW spokes)" disabled=yes \
listen-port=51821 mtu=1412 name=wg-hub
/interface vlan
add comment=MAIN interface=bridge name=vlan10 vlan-id=10
add comment=INFRA interface=bridge name=vlan20 vlan-id=20
add comment=IOT interface=bridge name=vlan30 vlan-id=30
add comment=MEDIA interface=bridge name=vlan40 vlan-id=40
add comment=UNTRUSTED interface=bridge name=vlan50 vlan-id=50
add comment=GUEST interface=bridge name=vlan90 vlan-id=90
add interface=bridge name=vlan99 vlan-id=99
add comment=WAN interface=bridge name=vlan100 vlan-id=100
/interface list
add comment=TP15 name=WAN
add comment=TP15 name=LAN
/ip pool
add name=pool-v10 ranges=10.40.10.100-10.40.10.200
add name=pool-v20 ranges=10.40.20.100-10.40.20.200
add name=pool-v30 ranges=10.40.30.100-10.40.30.250
add name=pool-v40 ranges=10.40.40.100-10.40.40.200
add name=pool-v50 ranges=10.40.50.100-10.40.50.200
add name=pool-v90 ranges=10.40.90.100-10.40.90.200
add name=pool-v99 ranges=10.40.99.100-10.40.99.200
/ip dhcp-server
add address-pool=pool-v10 comment=MAIN disabled=yes interface=vlan10 \
lease-time=1d name=dhcp-v10
add address-pool=pool-v20 comment=INFRA disabled=yes interface=vlan20 \
lease-time=1d name=dhcp-v20
add address-pool=pool-v30 comment=IOT disabled=yes interface=vlan30 \
lease-time=1d name=dhcp-v30
add address-pool=pool-v40 comment=MEDIA disabled=yes interface=vlan40 \
lease-time=1d name=dhcp-v40
add address-pool=pool-v50 comment=UNTRUSTED disabled=yes interface=vlan50 \
lease-time=1d name=dhcp-v50
add address-pool=pool-v90 comment=GUEST disabled=yes interface=vlan90 \
lease-time=12h name=dhcp-v90
add address-pool=pool-v99 comment=MGMT disabled=yes interface=vlan99 \
lease-time=1d name=dhcp-v99
/port
set 0 name=serial0
/interface bridge port
set [find interface=ether2] pvid=40
set [find interface=ether3] pvid=40
set [find interface=ether5] pvid=40
/interface bridge vlan
add bridge=bridge untagged=ether1,ether2 vlan-ids=1
add bridge=bridge tagged=ether1,ether4,ether8 vlan-ids=10
add bridge=bridge tagged=ether1,ether4,ether8 vlan-ids=20
add bridge=bridge tagged=ether1,ether4,ether8 vlan-ids=30
add bridge=bridge tagged=ether1,ether4,ether8 untagged=ether2,ether3,ether5 \
vlan-ids=40
add bridge=bridge tagged=ether1,ether4,ether8 vlan-ids=50
add bridge=bridge tagged=ether1,ether4,ether8 vlan-ids=90
add bridge=bridge tagged=ether1,ether4,ether8 vlan-ids=99
add bridge=bridge tagged=ether1 untagged=ether16 vlan-ids=100
/interface list member
add interface=vlan100 list=WAN
add interface=vlan10 list=LAN
add interface=vlan20 list=LAN
add interface=vlan30 list=LAN
add interface=vlan40 list=LAN
add interface=vlan50 list=LAN
add interface=vlan90 list=LAN
add interface=vlan99 list=LAN
/interface wireguard peers
add allowed-address=10.101.0.0/24 endpoint-address=\
abxhutfgsid9snmy.myfritz.net endpoint-port=52476 interface=wg-hub name=fm \
public-key="dzRY0lnnUygijChWe5wXZmXPeNcbdALpqBptA3fyiVE="
add allowed-address=10.95.10.0/24 interface=wg-hub name=dw public-key=\
"FqYZLztQ/qME5VDie5QLGH++5299WJiE1dPpQnPbzUw="
/ip address
add address=10.40.41.211/24 comment=TP15-bringup-vlan1 interface=bridge \
network=10.40.41.0
add address=10.40.99.11/24 comment=TP15-mgmt-target interface=vlan99 network=\
10.40.99.0
add address=10.40.10.1/24 comment="L3 SVI VLAN10" disabled=yes interface=\
vlan10 network=10.40.10.0
add address=10.40.20.1/24 comment="L3 SVI VLAN20" disabled=yes interface=\
vlan20 network=10.40.20.0
add address=10.40.30.1/24 comment="L3 SVI VLAN30" disabled=yes interface=\
vlan30 network=10.40.30.0
add address=10.40.40.1/24 comment="L3 SVI VLAN40" disabled=yes interface=\
vlan40 network=10.40.40.0
add address=10.40.50.1/24 comment="L3 SVI VLAN50" disabled=yes interface=\
vlan50 network=10.40.50.0
add address=10.40.90.1/24 comment="L3 SVI VLAN90" disabled=yes interface=\
vlan90 network=10.40.90.0
add address=10.40.99.1/24 comment="Mgmt SVI (VLAN99)" disabled=yes interface=\
vlan99 network=10.40.99.0
/ip dhcp-client
add comment="TP15 WAN Liwest (Arris)" disabled=yes interface=vlan100 \
use-peer-dns=no
/ip dhcp-server lease
add address=10.40.10.196 client-id=1:f2:62:4c:e1:af:9 comment=iPhone \
disabled=yes mac-address=F2:62:4C:E1:AF:09 server=dhcp-v10
add address=10.40.20.10 comment=nas-bf disabled=yes mac-address=\
90:09:D0:58:20:F9 server=dhcp-v20
add address=10.40.30.150 client-id=1:fc:ee:11:3:32:26 comment=\
"ingenic-3226 / Ender3V3KE-3226" disabled=yes mac-address=\
FC:EE:11:03:32:26 server=dhcp-v30
add address=10.40.10.82 client-id=1:56:71:6c:22:e6:a9 comment=Mac disabled=\
yes mac-address=56:71:6C:22:E6:A9 server=dhcp-v10
add address=10.40.30.180 client-id=1:e4:bc:96:1f:b2:81 comment=\
"espressif / Airfryer" disabled=yes mac-address=E4:BC:96:1F:B2:81 server=\
dhcp-v30
add address=10.40.30.202 comment=shelly-s22_kueche_steckdose3-2 disabled=yes \
mac-address=A0:85:E3:C8:65:34 server=dhcp-v30
add address=10.40.30.203 comment=shelly-s23_dieleug-wohnzimmer_licht \
disabled=yes mac-address=AC:15:18:7B:62:20 server=dhcp-v30
add address=10.40.30.204 comment=shelly-s24_bad_spiegelschrank disabled=yes \
mac-address=14:33:5C:D8:2D:7C server=dhcp-v30
add address=10.40.30.205 comment=shelly-s25_bad_lichtschalter disabled=yes \
mac-address=98:A3:16:79:38:50 server=dhcp-v30
add address=10.40.30.206 comment=shelly-s20_kueche_steckdosen2 disabled=yes \
mac-address=98:A3:16:7C:5F:EC server=dhcp-v30
add address=10.40.30.207 comment=shelly-s21_kueche_steckdosen4 disabled=yes \
mac-address=20:6E:F1:01:F9:F8 server=dhcp-v30
add address=10.40.30.208 comment=shelly-s1_weihnachten_kueche disabled=yes \
mac-address=E4:B0:63:D7:C7:A4 server=dhcp-v30
add address=10.40.30.209 comment=shelly-s2_weihnachten_terrasse disabled=yes \
mac-address=28:37:2F:36:F4:7C server=dhcp-v30
add address=10.40.30.210 comment=shelly-s3_weihnachten_kinderzimmer disabled=\
yes mac-address=E4:B0:63:E4:EF:7C server=dhcp-v30
add address=10.40.30.211 comment=shelly-s4_kueche_mikrowelle disabled=yes \
mac-address=E4:B0:63:E4:0F:30 server=dhcp-v30
add address=10.40.30.212 comment=shelly-s5_kueche_backofen disabled=yes \
mac-address=D8:85:AC:0C:5C:C8 server=dhcp-v30
add address=10.40.30.213 comment=shelly-s6_kueche_kuehlschrank disabled=yes \
mac-address=D0:CF:13:D8:18:FC server=dhcp-v30
add address=10.40.30.214 comment=shelly-s7_kueche_tiefkuehlschrank disabled=\
yes mac-address=D0:CF:13:D9:94:E8 server=dhcp-v30
add address=10.40.30.215 comment=shelly-s8_kueche_geschirrspueler disabled=\
yes mac-address=E4:B0:63:FA:FD:E0 server=dhcp-v30
add address=10.40.30.216 comment=shelly-s9_wohnzimmer_sideboard disabled=yes \
mac-address=E8:F6:0A:61:88:08 server=dhcp-v30
add address=10.40.30.217 comment=shelly-s10_bad_sonos disabled=yes \
mac-address=E4:B0:63:FC:57:E4 server=dhcp-v30
add address=10.40.30.218 comment=shelly-s11_kinderzimmer_sonos disabled=yes \
mac-address=E4:B0:63:FA:6C:00 server=dhcp-v30
add address=10.40.30.219 comment=shelly-s12_dieleug_steckdosen disabled=yes \
mac-address=48:F6:EE:C8:E6:AC server=dhcp-v30
add address=10.40.30.220 comment=shelly-s13_arbeitszimmer_technik disabled=\
yes mac-address=48:F6:EE:DD:6A:F4 server=dhcp-v30
add address=10.40.30.221 comment=shelly-s14_schlafzimmer_bett disabled=yes \
mac-address=E4:B0:63:FC:59:48 server=dhcp-v30
add address=10.40.30.222 comment=shelly-s15_technik_pve disabled=yes \
mac-address=E4:B0:63:FA:D5:34 server=dhcp-v30
add address=10.40.30.223 comment=shelly-s16_wohnzimmer_steckdosen disabled=\
yes mac-address=10:20:BA:FA:F5:40 server=dhcp-v30
add address=10.40.30.224 comment=shelly-s17_bad_steckdosen disabled=yes \
mac-address=10:20:BA:FB:6D:64 server=dhcp-v30
add address=10.40.30.225 comment=shelly-s18_kueche_steckdosen1 disabled=yes \
mac-address=48:F6:EE:CC:16:E4 server=dhcp-v30
add address=10.40.30.226 comment=shelly-s19_kueche_steckdosen3 disabled=yes \
mac-address=10:20:BA:FB:70:FC server=dhcp-v30
add address=10.40.30.227 comment="SwitchBot-floor-lamp / switchbot-floorlamp" \
disabled=yes mac-address=B8:F8:62:09:F2:04 server=dhcp-v30
add address=10.40.30.228 comment=\
"Twinkly-2CA789 / Twinkly_Weihnachtsgirlande" disabled=yes mac-address=\
98:F4:AB:2C:A7:88 server=dhcp-v30
add address=10.40.30.230 comment=Withings_waage disabled=yes mac-address=\
00:24:E4:40:2A:72 server=dhcp-v30
add address=10.40.30.231 comment=Withings_blutdruck disabled=yes mac-address=\
A4:7E:FA:18:2C:64 server=dhcp-v30
add address=10.40.30.232 comment=pve-homematic-raspi-ccu disabled=yes \
mac-address=BC:24:11:8E:0A:57 server=dhcp-v30
add address=10.40.30.233 comment=shelly-s26_bad_luefter disabled=yes \
mac-address=98:A3:16:7A:A3:54 server=dhcp-v30
add address=10.40.30.236 comment=lichterkette disabled=yes mac-address=\
A4:F0:0F:5F:D0:EC server=dhcp-v30
add address=10.40.30.237 comment=lichterkette-mini disabled=yes mac-address=\
24:DC:C3:9B:89:64 server=dhcp-v30
add address=10.40.30.241 comment=NukiSmartlock disabled=yes mac-address=\
28:DB:A7:09:7B:54 server=dhcp-v30
add address=10.40.30.244 comment=NukiBridge disabled=yes mac-address=\
24:62:AB:B1:63:F4 server=dhcp-v30
add address=10.40.30.5 comment="P750W / P750W" disabled=yes mac-address=\
4C:EB:BD:E3:25:F2 server=dhcp-v30
add address=10.40.30.8 comment="DTUBI-414392337823 / DTUBI-414392337823" \
disabled=yes mac-address=54:F2:9F:12:51:F0 server=dhcp-v30
add address=10.40.40.11 comment=tv disabled=yes mac-address=70:AF:24:B8:C9:86 \
server=dhcp-v40
add address=10.40.40.191 client-id=1:34:7e:5c:32:37:96 comment=SonosZP \
disabled=yes mac-address=34:7E:5C:32:37:96 server=dhcp-v40
add address=10.40.40.192 client-id=1:34:7e:5c:32:33:b8 comment=SonosZP \
disabled=yes mac-address=34:7E:5C:32:33:B8 server=dhcp-v40
add address=10.40.40.193 client-id=1:78:28:ca:80:2a:f6 comment=SonosZP \
disabled=yes mac-address=78:28:CA:80:2A:F6 server=dhcp-v40
add address=10.40.40.194 client-id=1:48:a6:b8:14:fe:d0 comment=SonosZP \
disabled=yes mac-address=48:A6:B8:14:FE:D0 server=dhcp-v40
add address=10.40.40.195 client-id=1:78:28:ca:82:16:c2 comment=SonosZP \
disabled=yes mac-address=78:28:CA:82:16:C2 server=dhcp-v40
add address=10.40.40.196 client-id=1:48:a6:b8:16:38:ae comment=SonosZP \
disabled=yes mac-address=48:A6:B8:16:38:AE server=dhcp-v40
add address=10.40.40.197 client-id=1:48:a6:b8:14:fe:f2 comment=SonosZP \
disabled=yes mac-address=48:A6:B8:14:FE:F2 server=dhcp-v40
add address=10.40.40.198 comment="SonosZP / SonosTerrasse" disabled=yes \
mac-address=78:28:CA:D2:0D:AC server=dhcp-v40
add address=10.40.40.199 client-id=1:48:a6:b8:b:63:10 comment=SonosZP \
disabled=yes mac-address=48:A6:B8:0B:63:10 server=dhcp-v40
add address=10.40.40.200 client-id=1:48:a6:b8:69:69:7b comment=SonosZP \
disabled=yes mac-address=48:A6:B8:69:69:7B server=dhcp-v40
add address=10.40.40.201 client-id=1:94:9f:3e:49:59:ee comment=SonosZP \
disabled=yes mac-address=94:9F:3E:49:59:EE server=dhcp-v40
/ip dhcp-server network
add address=10.40.10.0/24 dns-server=10.40.10.1 domain=bf.lan gateway=\
10.40.10.1
add address=10.40.20.0/24 dns-server=10.40.20.1 domain=bf.lan gateway=\
10.40.20.1
add address=10.40.30.0/24 dns-server=10.40.30.1 domain=bf.lan gateway=\
10.40.30.1
add address=10.40.40.0/24 dns-server=10.40.40.1 domain=bf.lan gateway=\
10.40.40.1
add address=10.40.50.0/24 dns-server=10.40.50.1 domain=bf.lan gateway=\
10.40.50.1
add address=10.40.90.0/24 dns-server=10.40.90.1 domain=bf.lan gateway=\
10.40.90.1
add address=10.40.99.0/24 dns-server=10.40.99.1 domain=bf.lan gateway=\
10.40.99.1
/ip dns
set allow-remote-requests=yes mdns-repeat-ifaces=\
vlan10,vlan20,vlan40,vlan30,vlan90 servers=1.1.1.1 use-doh-server=\
https://px35ynlol3.cloudflare-gateway.com/dns-query verify-doh-cert=yes
/ip dns static
add address=10.40.99.1 comment="core = GW/Router" name=router.lan type=A
add address=10.40.99.3 comment="ap1 = reiner AP" name=ap1.bf.lan type=A
add address=10.40.99.2 comment="ap2 = reiner AP" name=ap2.bf.lan type=A
add address=10.40.99.1 comment="crs326-core (L3)" name=core.bf.lan type=A
add address=10.40.99.12 comment=crs326-branch name=branch.bf.lan type=A
add address=10.40.20.167 name=macmini.bf.lan type=A
add address=10.40.20.100 name=pve.bf.lan type=A
add address=10.40.20.173 name=pve-mcp.bf.lan type=A
add address=10.40.20.10 name=nas.bf.lan type=A
add address=10.40.20.126 name=docker.bf.lan type=A
add address=10.40.20.20 name=platform-pg.bf.lan type=A
/ip firewall filter
add action=accept chain=forward comment="defconf: accept est,rel,untracked" \
connection-state=established,related,untracked
add action=fasttrack-connection chain=forward comment="defconf: fasttrack" \
connection-state=established,related hw-offload=yes
add action=drop chain=forward comment="defconf: drop invalid" \
connection-state=invalid
add action=drop chain=forward comment="defconf: drop WAN !dstnat" \
connection-nat-state=!dstnat in-interface-list=WAN
add action=accept chain=forward comment="TP10: allow from WG spokes (FM/DW)" \
in-interface=wg-hub
add action=accept chain=forward comment="TP10: allow to WG spokes (FM/DW)" \
out-interface=wg-hub
add action=accept chain=forward comment="TP5: MAIN->MEDIA" in-interface=\
vlan10 out-interface=vlan40
add action=accept chain=forward comment="TP5: MEDIA->MAIN" in-interface=\
vlan40 out-interface=vlan10
add action=accept chain=forward comment="TP5: INFRA->MEDIA" in-interface=\
vlan20 out-interface=vlan40
add action=accept chain=forward comment="TP5: MEDIA->INFRA" in-interface=\
vlan40 out-interface=vlan20
add action=accept chain=forward comment="TP5: GUEST->MEDIA" in-interface=\
vlan90 out-interface=vlan40
add action=accept chain=forward comment="TP5: MEDIA->GUEST" in-interface=\
vlan40 out-interface=vlan90
add action=accept chain=input comment=TP10-seal-est connection-state=\
established,related,untracked
add action=drop chain=input comment=TP10-seal-inv connection-state=invalid
add action=accept chain=input comment="TP10: WG hub listen" dst-port=51821 \
protocol=udp
add action=accept chain=input comment=TP10-seal-dhcp dst-port=68 \
in-interface-list=WAN protocol=udp
add action=accept chain=input comment=TP10-seal-icmp protocol=icmp
add action=accept chain=input comment=TP10-seal-lan in-interface-list=LAN
add action=drop chain=input comment=TP10-seal-wandrop in-interface-list=WAN
add action=accept chain=input comment="TP5: mDNS in vlan10" dst-port=5353 \
in-interface=vlan10 protocol=udp
add action=accept chain=input comment="TP5: mDNS in vlan40" dst-port=5353 \
in-interface=vlan40 protocol=udp
add action=accept chain=input comment="TP5: mDNS in vlan20" dst-port=5353 \
in-interface=vlan20 protocol=udp
add action=accept chain=input comment="TP5: mDNS in vlan30" dst-port=5353 \
in-interface=vlan30 protocol=udp
add action=accept chain=input comment="TP5: mDNS in vlan90" dst-port=5353 \
in-interface=vlan90 protocol=udp
/ip firewall nat
add action=accept chain=srcnat comment=\
"TP4: no-NAT inter-VLAN (vor masquerade)" dst-address=10.40.0.0/16
add action=masquerade chain=srcnat comment="defconf: masquerade WAN" \
out-interface-list=WAN
/ip hotspot profile
set [ find default=yes ] html-directory=hotspot
/ip route
add comment=TP15-bringup dst-address=0.0.0.0/0 gateway=10.40.41.1
add comment="TP10: FM spoke -> wg-hub" dst-address=10.101.0.0/24 gateway=\
wg-hub
add comment="TP10: DW spoke -> wg-hub" dst-address=10.95.10.0/24 gateway=\
wg-hub
/ip service
set ftp disabled=yes
set ssh address=10.40.0.0/16
set telnet disabled=yes
set www address=10.40.0.0/16
set api address=10.40.0.0/16
set api-ssl disabled=yes
/system identity
set name=crs326-core
/system routerboard settings
set enter-setup-on=delete-key

View File

@@ -0,0 +1,360 @@
# 2025-09-05 02:45:12 by RouterOS 7.19.5
# software id = 4W04-URHT
#
# model = CRS326-24G-2S+
# serial number = HKA0AREXJFM
/interface bridge
add admin-mac=04:F4:1C:B4:17:76 auto-mac=no comment=defconf name=bridge \
vlan-filtering=yes
/interface ethernet
set [ find default-name=ether3 ] disabled=yes
set [ find default-name=ether4 ] disabled=yes
set [ find default-name=ether5 ] disabled=yes
set [ find default-name=ether6 ] disabled=yes
set [ find default-name=ether7 ] disabled=yes
set [ find default-name=ether8 ] disabled=yes
set [ find default-name=ether9 ] disabled=yes
set [ find default-name=ether10 ] disabled=yes
set [ find default-name=ether11 ] disabled=yes
set [ find default-name=ether12 ] disabled=yes
set [ find default-name=ether13 ] disabled=yes
set [ find default-name=ether14 ] disabled=yes
set [ find default-name=ether15 ] disabled=yes
set [ find default-name=ether16 ] disabled=yes
set [ find default-name=ether17 ] disabled=yes
set [ find default-name=ether18 ] disabled=yes
set [ find default-name=ether19 ] disabled=yes
set [ find default-name=ether20 ] disabled=yes
set [ find default-name=ether21 ] disabled=yes
set [ find default-name=ether22 ] disabled=yes
set [ find default-name=ether23 ] disabled=yes
set [ find default-name=ether24 ] disabled=yes
set [ find default-name=sfp-sfpplus1 ] disabled=yes
set [ find default-name=sfp-sfpplus2 ] disabled=yes
/interface wireguard
add comment="TP10 Multi-Site VPN Hub (FM/DW spokes)" disabled=yes \
listen-port=51821 mtu=1412 name=wg-hub
/interface vlan
add comment=MAIN interface=bridge name=vlan10 vlan-id=10
add comment=INFRA interface=bridge name=vlan20 vlan-id=20
add comment=IOT interface=bridge name=vlan30 vlan-id=30
add comment=MEDIA interface=bridge name=vlan40 vlan-id=40
add comment=UNTRUSTED interface=bridge name=vlan50 vlan-id=50
add comment=GUEST interface=bridge name=vlan90 vlan-id=90
add interface=bridge name=vlan99 vlan-id=99
add comment=WAN interface=bridge name=vlan100 vlan-id=100
/interface list
add comment=TP15 name=WAN
add comment=TP15 name=LAN
/ip pool
add name=pool-v10 ranges=10.40.10.100-10.40.10.200
add name=pool-v20 ranges=10.40.20.100-10.40.20.200
add name=pool-v30 ranges=10.40.30.100-10.40.30.250
add name=pool-v40 ranges=10.40.40.100-10.40.40.200
add name=pool-v50 ranges=10.40.50.100-10.40.50.200
add name=pool-v90 ranges=10.40.90.100-10.40.90.200
add name=pool-v99 ranges=10.40.99.100-10.40.99.200
/ip dhcp-server
add address-pool=pool-v10 comment=MAIN disabled=yes interface=vlan10 \
lease-time=1d name=dhcp-v10
add address-pool=pool-v20 comment=INFRA disabled=yes interface=vlan20 \
lease-time=1d name=dhcp-v20
add address-pool=pool-v30 comment=IOT disabled=yes interface=vlan30 \
lease-time=1d name=dhcp-v30
add address-pool=pool-v40 comment=MEDIA disabled=yes interface=vlan40 \
lease-time=1d name=dhcp-v40
add address-pool=pool-v50 comment=UNTRUSTED disabled=yes interface=vlan50 \
lease-time=1d name=dhcp-v50
add address-pool=pool-v90 comment=GUEST disabled=yes interface=vlan90 \
lease-time=12h name=dhcp-v90
add address-pool=pool-v99 comment=MGMT disabled=yes interface=vlan99 \
lease-time=1d name=dhcp-v99
/port
set 0 name=serial0
/interface bridge port
add bridge=bridge comment=defconf interface=ether1
add bridge=bridge comment=defconf interface=ether2 pvid=40
add bridge=bridge comment=defconf interface=ether3 pvid=40
add bridge=bridge comment=defconf interface=ether4
add bridge=bridge comment=defconf interface=ether5 pvid=40
add bridge=bridge comment=defconf interface=ether6
add bridge=bridge comment=defconf interface=ether7
add bridge=bridge comment=defconf interface=ether8
add bridge=bridge comment=defconf interface=ether9
add bridge=bridge comment=defconf interface=ether10
add bridge=bridge comment=defconf interface=ether11
add bridge=bridge comment=defconf interface=ether12
add bridge=bridge comment=defconf interface=ether13
add bridge=bridge comment=defconf interface=ether14
add bridge=bridge comment=defconf interface=ether15
add bridge=bridge comment=defconf interface=ether16
add bridge=bridge comment=defconf interface=ether17
add bridge=bridge comment=defconf interface=ether18
add bridge=bridge comment=defconf interface=ether19
add bridge=bridge comment=defconf interface=ether20
add bridge=bridge comment=defconf interface=ether21
add bridge=bridge comment=defconf interface=ether22
add bridge=bridge comment=defconf interface=ether23
add bridge=bridge comment=defconf interface=ether24
add bridge=bridge comment=defconf interface=sfp-sfpplus1
add bridge=bridge comment=defconf interface=sfp-sfpplus2
/interface bridge vlan
add bridge=bridge untagged=ether1,ether2 vlan-ids=1
add bridge=bridge tagged=ether1,ether4,ether8 vlan-ids=10
add bridge=bridge tagged=ether1,ether4,ether8 vlan-ids=20
add bridge=bridge tagged=ether1,ether4,ether8 vlan-ids=30
add bridge=bridge tagged=ether1,ether4,ether8 untagged=ether2,ether3,ether5 \
vlan-ids=40
add bridge=bridge tagged=ether1,ether4,ether8 vlan-ids=50
add bridge=bridge tagged=ether1,ether4,ether8 vlan-ids=90
add bridge=bridge tagged=ether1,ether4,ether8 vlan-ids=99
add bridge=bridge tagged=ether1 untagged=ether16 vlan-ids=100
/interface list member
add interface=vlan100 list=WAN
add interface=vlan10 list=LAN
add interface=vlan20 list=LAN
add interface=vlan30 list=LAN
add interface=vlan40 list=LAN
add interface=vlan50 list=LAN
add interface=vlan90 list=LAN
add interface=vlan99 list=LAN
/interface wireguard peers
add allowed-address=10.101.0.0/24 endpoint-address=\
abxhutfgsid9snmy.myfritz.net endpoint-port=52476 interface=wg-hub name=fm \
public-key="dzRY0lnnUygijChWe5wXZmXPeNcbdALpqBptA3fyiVE="
add allowed-address=10.95.10.0/24 interface=wg-hub name=dw public-key=\
"FqYZLztQ/qME5VDie5QLGH++5299WJiE1dPpQnPbzUw="
/ip address
add address=10.40.41.211/24 comment=TP15-bringup-vlan1 interface=bridge \
network=10.40.41.0
add address=10.40.99.11/24 comment=TP15-mgmt-target interface=vlan99 network=\
10.40.99.0
add address=10.40.10.1/24 comment="L3 SVI VLAN10" disabled=yes interface=\
vlan10 network=10.40.10.0
add address=10.40.20.1/24 comment="L3 SVI VLAN20" disabled=yes interface=\
vlan20 network=10.40.20.0
add address=10.40.30.1/24 comment="L3 SVI VLAN30" disabled=yes interface=\
vlan30 network=10.40.30.0
add address=10.40.40.1/24 comment="L3 SVI VLAN40" disabled=yes interface=\
vlan40 network=10.40.40.0
add address=10.40.50.1/24 comment="L3 SVI VLAN50" disabled=yes interface=\
vlan50 network=10.40.50.0
add address=10.40.90.1/24 comment="L3 SVI VLAN90" disabled=yes interface=\
vlan90 network=10.40.90.0
add address=10.40.99.1/24 comment="Mgmt SVI (VLAN99)" disabled=yes interface=\
vlan99 network=10.40.99.0
/ip dhcp-client
add comment="TP15 WAN Liwest (Arris)" disabled=yes interface=vlan100 \
use-peer-dns=no
/ip dhcp-server lease
add address=10.40.10.196 client-id=1:f2:62:4c:e1:af:9 comment=iPhone \
disabled=yes mac-address=F2:62:4C:E1:AF:09 server=dhcp-v10
add address=10.40.20.10 comment=nas-bf disabled=yes mac-address=\
90:09:D0:58:20:F9 server=dhcp-v20
add address=10.40.30.150 client-id=1:fc:ee:11:3:32:26 comment=\
"ingenic-3226 / Ender3V3KE-3226" disabled=yes mac-address=\
FC:EE:11:03:32:26 server=dhcp-v30
add address=10.40.10.82 client-id=1:56:71:6c:22:e6:a9 comment=Mac disabled=\
yes mac-address=56:71:6C:22:E6:A9 server=dhcp-v10
add address=10.40.30.180 client-id=1:e4:bc:96:1f:b2:81 comment=\
"espressif / Airfryer" disabled=yes mac-address=E4:BC:96:1F:B2:81 server=\
dhcp-v30
add address=10.40.30.202 comment=shelly-s22_kueche_steckdose3-2 disabled=yes \
mac-address=A0:85:E3:C8:65:34 server=dhcp-v30
add address=10.40.30.203 comment=shelly-s23_dieleug-wohnzimmer_licht \
disabled=yes mac-address=AC:15:18:7B:62:20 server=dhcp-v30
add address=10.40.30.204 comment=shelly-s24_bad_spiegelschrank disabled=yes \
mac-address=14:33:5C:D8:2D:7C server=dhcp-v30
add address=10.40.30.205 comment=shelly-s25_bad_lichtschalter disabled=yes \
mac-address=98:A3:16:79:38:50 server=dhcp-v30
add address=10.40.30.206 comment=shelly-s20_kueche_steckdosen2 disabled=yes \
mac-address=98:A3:16:7C:5F:EC server=dhcp-v30
add address=10.40.30.207 comment=shelly-s21_kueche_steckdosen4 disabled=yes \
mac-address=20:6E:F1:01:F9:F8 server=dhcp-v30
add address=10.40.30.208 comment=shelly-s1_weihnachten_kueche disabled=yes \
mac-address=E4:B0:63:D7:C7:A4 server=dhcp-v30
add address=10.40.30.209 comment=shelly-s2_weihnachten_terrasse disabled=yes \
mac-address=28:37:2F:36:F4:7C server=dhcp-v30
add address=10.40.30.210 comment=shelly-s3_weihnachten_kinderzimmer disabled=\
yes mac-address=E4:B0:63:E4:EF:7C server=dhcp-v30
add address=10.40.30.211 comment=shelly-s4_kueche_mikrowelle disabled=yes \
mac-address=E4:B0:63:E4:0F:30 server=dhcp-v30
add address=10.40.30.212 comment=shelly-s5_kueche_backofen disabled=yes \
mac-address=D8:85:AC:0C:5C:C8 server=dhcp-v30
add address=10.40.30.213 comment=shelly-s6_kueche_kuehlschrank disabled=yes \
mac-address=D0:CF:13:D8:18:FC server=dhcp-v30
add address=10.40.30.214 comment=shelly-s7_kueche_tiefkuehlschrank disabled=\
yes mac-address=D0:CF:13:D9:94:E8 server=dhcp-v30
add address=10.40.30.215 comment=shelly-s8_kueche_geschirrspueler disabled=\
yes mac-address=E4:B0:63:FA:FD:E0 server=dhcp-v30
add address=10.40.30.216 comment=shelly-s9_wohnzimmer_sideboard disabled=yes \
mac-address=E8:F6:0A:61:88:08 server=dhcp-v30
add address=10.40.30.217 comment=shelly-s10_bad_sonos disabled=yes \
mac-address=E4:B0:63:FC:57:E4 server=dhcp-v30
add address=10.40.30.218 comment=shelly-s11_kinderzimmer_sonos disabled=yes \
mac-address=E4:B0:63:FA:6C:00 server=dhcp-v30
add address=10.40.30.219 comment=shelly-s12_dieleug_steckdosen disabled=yes \
mac-address=48:F6:EE:C8:E6:AC server=dhcp-v30
add address=10.40.30.220 comment=shelly-s13_arbeitszimmer_technik disabled=\
yes mac-address=48:F6:EE:DD:6A:F4 server=dhcp-v30
add address=10.40.30.221 comment=shelly-s14_schlafzimmer_bett disabled=yes \
mac-address=E4:B0:63:FC:59:48 server=dhcp-v30
add address=10.40.30.222 comment=shelly-s15_technik_pve disabled=yes \
mac-address=E4:B0:63:FA:D5:34 server=dhcp-v30
add address=10.40.30.223 comment=shelly-s16_wohnzimmer_steckdosen disabled=\
yes mac-address=10:20:BA:FA:F5:40 server=dhcp-v30
add address=10.40.30.224 comment=shelly-s17_bad_steckdosen disabled=yes \
mac-address=10:20:BA:FB:6D:64 server=dhcp-v30
add address=10.40.30.225 comment=shelly-s18_kueche_steckdosen1 disabled=yes \
mac-address=48:F6:EE:CC:16:E4 server=dhcp-v30
add address=10.40.30.226 comment=shelly-s19_kueche_steckdosen3 disabled=yes \
mac-address=10:20:BA:FB:70:FC server=dhcp-v30
add address=10.40.30.227 comment="SwitchBot-floor-lamp / switchbot-floorlamp" \
disabled=yes mac-address=B8:F8:62:09:F2:04 server=dhcp-v30
add address=10.40.30.228 comment=\
"Twinkly-2CA789 / Twinkly_Weihnachtsgirlande" disabled=yes mac-address=\
98:F4:AB:2C:A7:88 server=dhcp-v30
add address=10.40.30.230 comment=Withings_waage disabled=yes mac-address=\
00:24:E4:40:2A:72 server=dhcp-v30
add address=10.40.30.231 comment=Withings_blutdruck disabled=yes mac-address=\
A4:7E:FA:18:2C:64 server=dhcp-v30
add address=10.40.30.232 comment=pve-homematic-raspi-ccu disabled=yes \
mac-address=BC:24:11:8E:0A:57 server=dhcp-v30
add address=10.40.30.233 comment=shelly-s26_bad_luefter disabled=yes \
mac-address=98:A3:16:7A:A3:54 server=dhcp-v30
add address=10.40.30.236 comment=lichterkette disabled=yes mac-address=\
A4:F0:0F:5F:D0:EC server=dhcp-v30
add address=10.40.30.237 comment=lichterkette-mini disabled=yes mac-address=\
24:DC:C3:9B:89:64 server=dhcp-v30
add address=10.40.30.241 comment=NukiSmartlock disabled=yes mac-address=\
28:DB:A7:09:7B:54 server=dhcp-v30
add address=10.40.30.244 comment=NukiBridge disabled=yes mac-address=\
24:62:AB:B1:63:F4 server=dhcp-v30
add address=10.40.30.5 comment="P750W / P750W" disabled=yes mac-address=\
4C:EB:BD:E3:25:F2 server=dhcp-v30
add address=10.40.30.8 comment="DTUBI-414392337823 / DTUBI-414392337823" \
disabled=yes mac-address=54:F2:9F:12:51:F0 server=dhcp-v30
add address=10.40.40.11 comment=tv disabled=yes mac-address=70:AF:24:B8:C9:86 \
server=dhcp-v40
add address=10.40.40.191 client-id=1:34:7e:5c:32:37:96 comment=SonosZP \
disabled=yes mac-address=34:7E:5C:32:37:96 server=dhcp-v40
add address=10.40.40.192 client-id=1:34:7e:5c:32:33:b8 comment=SonosZP \
disabled=yes mac-address=34:7E:5C:32:33:B8 server=dhcp-v40
add address=10.40.40.193 client-id=1:78:28:ca:80:2a:f6 comment=SonosZP \
disabled=yes mac-address=78:28:CA:80:2A:F6 server=dhcp-v40
add address=10.40.40.194 client-id=1:48:a6:b8:14:fe:d0 comment=SonosZP \
disabled=yes mac-address=48:A6:B8:14:FE:D0 server=dhcp-v40
add address=10.40.40.195 client-id=1:78:28:ca:82:16:c2 comment=SonosZP \
disabled=yes mac-address=78:28:CA:82:16:C2 server=dhcp-v40
add address=10.40.40.196 client-id=1:48:a6:b8:16:38:ae comment=SonosZP \
disabled=yes mac-address=48:A6:B8:16:38:AE server=dhcp-v40
add address=10.40.40.197 client-id=1:48:a6:b8:14:fe:f2 comment=SonosZP \
disabled=yes mac-address=48:A6:B8:14:FE:F2 server=dhcp-v40
add address=10.40.40.198 comment="SonosZP / SonosTerrasse" disabled=yes \
mac-address=78:28:CA:D2:0D:AC server=dhcp-v40
add address=10.40.40.199 client-id=1:48:a6:b8:b:63:10 comment=SonosZP \
disabled=yes mac-address=48:A6:B8:0B:63:10 server=dhcp-v40
add address=10.40.40.200 client-id=1:48:a6:b8:69:69:7b comment=SonosZP \
disabled=yes mac-address=48:A6:B8:69:69:7B server=dhcp-v40
add address=10.40.40.201 client-id=1:94:9f:3e:49:59:ee comment=SonosZP \
disabled=yes mac-address=94:9F:3E:49:59:EE server=dhcp-v40
/ip dhcp-server network
add address=10.40.10.0/24 dns-server=10.40.10.1 domain=bf.lan gateway=\
10.40.10.1
add address=10.40.20.0/24 dns-server=10.40.20.1 domain=bf.lan gateway=\
10.40.20.1
add address=10.40.30.0/24 dns-server=10.40.30.1 domain=bf.lan gateway=\
10.40.30.1
add address=10.40.40.0/24 dns-server=10.40.40.1 domain=bf.lan gateway=\
10.40.40.1
add address=10.40.50.0/24 dns-server=10.40.50.1 domain=bf.lan gateway=\
10.40.50.1
add address=10.40.90.0/24 dns-server=10.40.90.1 domain=bf.lan gateway=\
10.40.90.1
add address=10.40.99.0/24 dns-server=10.40.99.1 domain=bf.lan gateway=\
10.40.99.1
/ip dns
set allow-remote-requests=yes mdns-repeat-ifaces=\
vlan10,vlan20,vlan40,vlan30,vlan90 servers=1.1.1.1 use-doh-server=\
https://px35ynlol3.cloudflare-gateway.com/dns-query verify-doh-cert=yes
/ip dns static
add address=10.40.99.1 comment="core = GW/Router" name=router.lan type=A
add address=10.40.99.3 comment="ap1 = reiner AP" name=ap1.bf.lan type=A
add address=10.40.99.2 comment="ap2 = reiner AP" name=ap2.bf.lan type=A
add address=10.40.99.1 comment="crs326-core (L3)" name=core.bf.lan type=A
add address=10.40.99.12 comment=crs326-branch name=branch.bf.lan type=A
add address=10.40.20.167 name=macmini.bf.lan type=A
add address=10.40.20.100 name=pve.bf.lan type=A
add address=10.40.20.173 name=pve-mcp.bf.lan type=A
add address=10.40.20.10 name=nas.bf.lan type=A
add address=10.40.20.126 name=docker.bf.lan type=A
add address=10.40.20.20 name=platform-pg.bf.lan type=A
/ip firewall filter
add action=accept chain=forward comment="defconf: accept est,rel,untracked" \
connection-state=established,related,untracked
add action=fasttrack-connection chain=forward comment="defconf: fasttrack" \
connection-state=established,related hw-offload=yes
add action=drop chain=forward comment="defconf: drop invalid" \
connection-state=invalid
add action=drop chain=forward comment="defconf: drop WAN !dstnat" \
connection-nat-state=!dstnat in-interface-list=WAN
add action=accept chain=forward comment="TP10: allow from WG spokes (FM/DW)" \
in-interface=wg-hub
add action=accept chain=forward comment="TP10: allow to WG spokes (FM/DW)" \
out-interface=wg-hub
add action=accept chain=forward comment="TP5: MAIN->MEDIA" in-interface=\
vlan10 out-interface=vlan40
add action=accept chain=forward comment="TP5: MEDIA->MAIN" in-interface=\
vlan40 out-interface=vlan10
add action=accept chain=forward comment="TP5: INFRA->MEDIA" in-interface=\
vlan20 out-interface=vlan40
add action=accept chain=forward comment="TP5: MEDIA->INFRA" in-interface=\
vlan40 out-interface=vlan20
add action=accept chain=forward comment="TP5: GUEST->MEDIA" in-interface=\
vlan90 out-interface=vlan40
add action=accept chain=forward comment="TP5: MEDIA->GUEST" in-interface=\
vlan40 out-interface=vlan90
add action=accept chain=input comment=TP10-seal-est connection-state=\
established,related,untracked
add action=drop chain=input comment=TP10-seal-inv connection-state=invalid
add action=accept chain=input comment="TP10: WG hub listen" dst-port=51821 \
protocol=udp
add action=accept chain=input comment=TP10-seal-dhcp dst-port=68 \
in-interface-list=WAN protocol=udp
add action=accept chain=input comment=TP10-seal-icmp protocol=icmp
add action=accept chain=input comment=TP10-seal-lan in-interface-list=LAN
add action=drop chain=input comment=TP10-seal-wandrop in-interface-list=WAN
add action=accept chain=input comment="TP5: mDNS in vlan10" dst-port=5353 \
in-interface=vlan10 protocol=udp
add action=accept chain=input comment="TP5: mDNS in vlan40" dst-port=5353 \
in-interface=vlan40 protocol=udp
add action=accept chain=input comment="TP5: mDNS in vlan20" dst-port=5353 \
in-interface=vlan20 protocol=udp
add action=accept chain=input comment="TP5: mDNS in vlan30" dst-port=5353 \
in-interface=vlan30 protocol=udp
add action=accept chain=input comment="TP5: mDNS in vlan90" dst-port=5353 \
in-interface=vlan90 protocol=udp
/ip firewall nat
add action=accept chain=srcnat comment=\
"TP4: no-NAT inter-VLAN (vor masquerade)" dst-address=10.40.0.0/16
add action=masquerade chain=srcnat comment="defconf: masquerade WAN" \
out-interface-list=WAN
/ip hotspot profile
set [ find default=yes ] html-directory=hotspot
/ip route
add comment=TP15-bringup dst-address=0.0.0.0/0 gateway=10.40.41.1
add comment="TP10: FM spoke -> wg-hub" dst-address=10.101.0.0/24 gateway=\
wg-hub
add comment="TP10: DW spoke -> wg-hub" dst-address=10.95.10.0/24 gateway=\
wg-hub
/ip service
set ftp disabled=yes
set ssh address=10.40.0.0/16
set telnet disabled=yes
set www address=10.40.0.0/16
set api address=10.40.0.0/16
set api-ssl disabled=yes
/system identity
set name=crs326-core
/system routerboard settings
set enter-setup-on=delete-key

View File

@@ -0,0 +1,119 @@
#!/usr/bin/env python3
"""Export ap1-Daten, die im crs326-core Cutover-Fenster gebraucht werden.
Ausgaben (ohne Secrets im stdout):
- routeros/ap1-leases-export.json : alle DHCP-Leases (static + dynamic)
- routeros/ap1-wg-hub.privkey : wg-hub private-key (nur diese Zeile)
- routeros/ap1-cutover-summary.txt : menschliche Zusammenfassung
Quelle: ap1 via REST (10.40.10.1), Creds aus ~/.claude.json (mikromcp-env).
"""
import base64
import json
import os
import pathlib
import sys
import urllib.request
import urllib.error
AP1_REST = "http://10.40.10.1/rest" # VLAN10 Mgmt-IP (ap1-Mgmt nur via VLAN10)
CLAUDE_JSON = pathlib.Path.home() / ".claude.json"
OUT_DIR = pathlib.Path(__file__).resolve().parent
def load_ap1_creds():
cfg = json.loads(CLAUDE_JSON.read_text())
env = cfg.get("mcpServers", {}).get("mikromcp", {}).get("env", {})
user = env.get("ROUTER_AP1_USER") or env.get("ROUTER_USER")
pw = env.get("ROUTER_AP1_PASS") or env.get("ROUTER_PASS")
if not user or not pw:
raise SystemExit("ROUTER_AP1_USER/PASS nicht in ~/.claude.json mikromcp.env gefunden")
return user, pw
def rest_get(path, user, pw):
url = f"{AP1_REST}{path}"
auth = base64.b64encode(f"{user}:{pw}".encode()).decode()
req = urllib.request.Request(url, headers={
"Authorization": f"Basic {auth}",
"Accept": "application/json",
})
try:
with urllib.request.urlopen(req, timeout=20) as resp:
raw = resp.read()
return json.loads(raw) if raw else []
except urllib.error.HTTPError as e:
sys.stderr.write(f"!! GET {url} -> {e.code} {e.read().decode()[:500]}\n")
raise
def main():
user, pw = load_ap1_creds()
print(f"ap1 REST {AP1_REST} (User: {user})")
# 1) DHCP-Leases
leases = rest_get("/ip/dhcp-server/lease", user, pw)
leases_path = OUT_DIR / "ap1-leases-export.json"
leases_path.write_text(json.dumps(leases, indent=2))
print(f" DHCP-Leases: {len(leases)} -> {leases_path}")
# 2) WG-Hub privkey (field may be redacted; fallback to script-run)
wg = rest_get("/interface/wireguard", user, pw)
hub = next((i for i in wg if i.get("name") == "wg-hub"), None)
privkey = ""
if hub:
privkey = hub.get("private-key") or ""
if not privkey:
# REST listet privkey manchmal nicht; über system/script ausgeben lassen
script = ':put [/interface/wireguard/get [find name="wg-hub"] private-key]'
try:
run_resp = rest_post("/system/script/run", {"source": script}, user, pw)
# output format variiert
privkey = str(run_resp).strip().strip('"')
except Exception as e:
sys.stderr.write(f" WG-Privkey-Fallback fehlgeschlagen: {e}\n")
wg_path = OUT_DIR / "ap1-wg-hub.privkey"
if privkey and privkey != "<<<none>>>":
wg_path.write_text(privkey + "\n")
print(f" WG-Hub Privkey -> {wg_path} ({len(privkey)} chars)")
else:
print(f" WARN: WG-Hub Privkey nicht ermittelt -> {wg_path} nicht geschrieben")
# 3) Zusammenfassung
summary = [
f"ap1 Cutover-Export vom {os.popen('date -Iseconds').read().strip()}",
f"DHCP-Leases gesamt: {len(leases)}",
f" statisch (dynamic=no): {sum(1 for l in leases if str(l.get('dynamic')).lower() == 'false')}",
f" dynamisch: {sum(1 for l in leases if str(l.get('dynamic')).lower() != 'false')}",
f"WG-Hub private-key erfasst: {'ja' if privkey else 'nein'}",
"",
"Nächste Schritte im Cutover-Fenster:",
"1. ap1-leases-export.json filtern: nur dynamic=no, server-Namen auf core dhcp-v10..v99 mappen.",
"2. ap1-wg-hub.privkey in core-Config einsetzen (ersetzt <<< AP1_WG_HUB_PRIVKEY >>>).",
"3. Generate Intended Config für crs326-core erneut laufen lassen -> Nautobot-Deployment.",
]
summary_path = OUT_DIR / "ap1-cutover-summary.txt"
summary_path.write_text("\n".join(summary) + "\n")
print(f" Summary -> {summary_path}")
def rest_post(path, data, user, pw):
url = f"{AP1_REST}{path}"
auth = base64.b64encode(f"{user}:{pw}".encode()).decode()
body = json.dumps(data).encode()
req = urllib.request.Request(url, data=body, headers={
"Authorization": f"Basic {auth}",
"Content-Type": "application/json",
"Accept": "application/json",
}, method="POST")
try:
with urllib.request.urlopen(req, timeout=20) as resp:
raw = resp.read()
return json.loads(raw) if raw else {}
except urllib.error.HTTPError as e:
sys.stderr.write(f"!! POST {url} -> {e.code} {e.read().decode()[:500]}\n")
raise
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,51 @@
#!/usr/bin/env python3
"""Holt crs326-core intended config aus Nautobot, setzt WG-Privkey,
fügt ap1 static leases hinzu, speichert als tp15-core-cutover-intended.rsc.
"""
import json
import pathlib
import re
import urllib.request
NAUTOBOT_URL = "http://10.40.20.30:8080"
TOKEN_PATH = pathlib.Path(__file__).parent.parent / "docs" / "nas-vlan20" / "stacks" / "210-nautobot.yml"
OUT = pathlib.Path(__file__).with_name("tp15-core-cutover-intended.rsc")
LEASES_RSC = pathlib.Path(__file__).with_name("ap1-leases-to-core.rsc")
PRIVKEY_PATH = pathlib.Path(__file__).with_name("ap1-wg-hub.privkey")
def get_token():
text = TOKEN_PATH.read_text()
m = re.search(r"^NAUTOBOT_SUPERUSER_API_TOKEN=(.+)$", text, re.MULTILINE)
if not m:
raise SystemExit("NAUTOBOT_SUPERUSER_API_TOKEN not found")
return m.group(1).strip()
def main():
token = get_token()
req = urllib.request.Request(
f"{NAUTOBOT_URL}/api/plugins/golden-config/golden-config/?device=crs326-core",
headers={"Authorization": f"Token {token}", "Accept": "application/json"},
)
with urllib.request.urlopen(req) as resp:
data = json.loads(resp.read().decode())
config = data["results"][0]["intended_config"]
privkey = PRIVKEY_PATH.read_text().strip()
if "<<< AP1_WG_HUB_PRIVKEY >>>" not in config:
raise SystemExit("placeholder missing in intended config")
config = config.replace("<<< AP1_WG_HUB_PRIVKEY >>>", privkey)
leases = LEASES_RSC.read_text()
# Entferne Kommentarblock-Lease-Datei, damit nur Befehle bleiben
lease_lines = [line for line in leases.splitlines() if line and not line.startswith("#")]
# Füge Leases vor dem WG-HUB-Block ein? Oder ans Ende. Ans Ende ist einfacher.
out = config.rstrip() + "\n\n# [9] ap1 static leases -> core (STAGING)" + "\n" + "\n".join(lease_lines) + "\n"
OUT.write_text(out)
print(f"{OUT} written ({len(out)} bytes)")
if __name__ == "__main__":
main()

37
routeros/fix-core-rsc.py Normal file
View File

@@ -0,0 +1,37 @@
#!/usr/bin/env python3
import re
src = "/Users/christophemsenhuber/dev/bf-network-r6/routeros/crs326-core-pre-cutover-20260806-073712.rsc"
dst = "/Users/christophemsenhuber/dev/bf-network-r6/routeros/crs326-core-pre-cutover-20260806-073712-fixed.rsc"
with open(src) as f:
lines = f.readlines()
out = []
i = 0
while i < len(lines):
line = lines[i]
if line.startswith("add admin-mac=04:F4:1C:B4:17:76 auto-mac=no comment=defconf name=bridge"):
out.append("set [find name=bridge] admin-mac=04:F4:1C:B4:17:76 auto-mac=no vlan-filtering=yes\n")
i += 1
while i < len(lines) and (lines[i].startswith(" ") or lines[i].strip().startswith("vlan-filtering")):
i += 1
continue
if re.match(r'set \[ find default-name=ether(3|4|5|6|7|8|9|1[0-9]|2[0-4]) \] disabled=yes', line):
i += 1
continue
if line.startswith("/interface bridge port"):
out.append(line)
i += 1
while i < len(lines) and not lines[i].startswith("/"):
i += 1
out.append("set [find interface=ether2] pvid=40\n")
out.append("set [find interface=ether3] pvid=40\n")
out.append("set [find interface=ether5] pvid=40\n")
continue
out.append(line)
i += 1
with open(dst, "w") as f:
f.writelines(out)
print("fixed rsc written to", dst)

View File

@@ -0,0 +1,96 @@
#!/usr/bin/env python3
"""Kopiert ein beliebiges .rsc auf crs326-core und importiert es via SSH.
Usage: python3 import-core-rsc.py <pfad/zur/datei.rsc> [--host 10.40.41.211]
Creds kommen aus ~/.claude.json (mcpServers.mikromcp.env) envPrefix CRS326_CORE_.
"""
import json
import os
import re
import subprocess
import sys
from pathlib import Path
CLAUDe_JSON = Path.home() / ".claude.json"
RSC_PATH = Path(sys.argv[1]).resolve() if len(sys.argv) > 1 else None
HOST = sys.argv[2] if len(sys.argv) > 2 and sys.argv[1] == "--host" else None
def usage():
print(__doc__, file=sys.stderr)
sys.exit(2)
if RSC_PATH is None:
usage()
if not RSC_PATH.exists():
sys.exit(f"Datei nicht gefunden: {RSC_PATH}")
if HOST is None:
HOST = "10.40.41.211"
# Creds aus Claude-Settings laden
env = json.loads(CLAUDe_JSON.read_text())["mcpServers"]["mikromcp"]["env"]
get = lambda key, default=None: env.get(key, default)
user = get("CRS326_CORE_USER", "admin")
pw = get("CRS326_CORE_PASS")
if not pw:
sys.exit("CRS326_CORE_PASS fehlt in ~/.claude.json")
# Remote-Dateiname: alphanumerisch + Unterstrich/Punkt/Minus
remote_name = re.sub(r"[^A-Za-z0-9_.-]", "_", RSC_PATH.name)
def ssh_cmd(remote_command: str) -> list:
return [
"sshpass", "-p", pw,
"ssh",
"-o", "StrictHostKeyChecking=no",
"-o", "UserKnownHostsFile=/dev/null",
"-o", "PubkeyAuthentication=no",
"-o", "LogLevel=ERROR",
f"{user}@{HOST}",
remote_command,
]
def run_silent(cmd: list):
r = subprocess.run(cmd, capture_output=True, text=True)
if r.returncode != 0:
out = (r.stdout + r.stderr).replace(pw, "***")
raise SystemExit(f"SSH-Befehl fehlgeschlagen (rc={r.returncode}):\n{out}")
return r
# 1) Eventuell vorhandene alte Remote-Datei entfernen
run_silent(ssh_cmd(f"/file/remove [find name=\"{remote_name}\"]; :delay 500ms"))
# 2) SCP
scp = [
"sshpass", "-p", pw,
"scp",
"-o", "StrictHostKeyChecking=no",
"-o", "UserKnownHostsFile=/dev/null",
"-o", "PubkeyAuthentication=no",
"-o", "LogLevel=ERROR",
str(RSC_PATH),
f"{user}@{HOST}:/{remote_name}",
]
r = subprocess.run(scp, capture_output=True, text=True)
if r.returncode != 0:
out = (r.stdout + r.stderr).replace(pw, "***")
raise SystemExit(f"SCP fehlgeschlagen (rc={r.returncode}):\n{out}")
# 3) Import — mit verbose, damit wir die Zeile sehen, wenn ein Fehler auftritt
import_cmd = f"/import file-name={remote_name} verbose=yes"
res = subprocess.run(ssh_cmd(import_cmd), capture_output=True, text=True)
out = (res.stdout + res.stderr).replace(pw, "***")
if res.returncode != 0:
raise SystemExit(f"Import fehlgeschlagen (rc={res.returncode}):\n{out}")
print(out)
print(f"OK: {RSC_PATH.name} importiert auf {HOST} als /{remote_name}")

View File

@@ -0,0 +1,59 @@
#!/usr/bin/env python3
"""Push nautobot/mikrotik_routeros.j2 to the bf-golden-config Gitea repo.
Reads Gitea token from ~/.secrets/credentials.env (GITEA_ACCESS_TOKEN).
"""
import base64
import json
import pathlib
import re
import urllib.request
REPO = "https://gitea.emsenhuber.net/api/v1/repos/admin/bf-golden-config"
FILE = "jinja_templates/mikrotik_routeros.j2"
TOKEN_PATH = pathlib.Path.home() / ".secrets/credentials.env"
def get_token():
text = TOKEN_PATH.read_text()
m = re.search(r"^GITEA_ACCESS_TOKEN=(.+)$", text, re.MULTILINE)
if not m:
raise SystemExit("GITEA_ACCESS_TOKEN not found")
return m.group(1).strip()
def main():
token = get_token()
src = pathlib.Path(__file__).parent.parent / "nautobot" / "mikrotik_routeros.j2"
content = src.read_text()
# fetch current SHA
req = urllib.request.Request(
f"{REPO}/contents/{FILE}?ref=main",
headers={"Authorization": f"token {token}"},
)
with urllib.request.urlopen(req) as resp:
meta = json.loads(resp.read().decode())
sha = meta["sha"]
payload = {
"content": base64.b64encode(content.encode()).decode(),
"sha": sha,
"message": "TP15: WG-HUB private key + ap1 cutover lease prep",
"branch": "main",
}
req2 = urllib.request.Request(
f"{REPO}/contents/{FILE}",
data=json.dumps(payload).encode(),
headers={
"Authorization": f"token {token}",
"Content-Type": "application/json",
},
method="PUT",
)
with urllib.request.urlopen(req2) as resp:
print(resp.status, json.loads(resp.read().decode()).get("commit", {}).get("id", "?"))
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,99 @@
#!/usr/bin/env python3
"""Lädt eine .rsc-/Script-Datei per RouterOS REST API auf crs326-core hoch
und führt sie als /system/script aus.
Usage: python3 rest-core-script.py <pfad/zur/datei.rsc> [--host 10.40.41.211]
"""
import json
import sys
import urllib.request
from pathlib import Path
CLAUDe_JSON = Path.home() / ".claude.json"
if len(sys.argv) < 2:
print(__doc__, file=sys.stderr)
sys.exit(2)
src_path = Path(sys.argv[1]).resolve()
host = "10.40.41.211"
if len(sys.argv) > 2 and sys.argv[2] == "--host" and len(sys.argv) > 3:
host = sys.argv[3]
if not src_path.exists():
sys.exit(f"Datei nicht gefunden: {src_path}")
env = json.loads(CLAUDe_JSON.read_text())["mcpServers"]["mikromcp"]["env"]
user = env.get("CRS326_CORE_USER", "admin")
pw = env.get("CRS326_CORE_PASS")
if not pw:
sys.exit("CRS326_CORE_PASS fehlt in ~/.claude.json")
base_url = f"http://{host}/rest"
script_name = f"tp15_{src_path.stem}"
source = src_path.read_text()
def api(path, body=None, method="POST"):
url = base_url + path
data = json.dumps(body).encode() if body is not None else None
req = urllib.request.Request(url, data=data, method=method)
req.add_header("Content-Type", "application/json")
import base64
req.add_header("Authorization", "Basic " + base64.b64encode(f"{user}:{pw}".encode()).decode())
try:
with urllib.request.urlopen(req, timeout=30) as resp:
return resp.status, resp.read().decode()
except urllib.error.HTTPError as e:
return e.code, (e.read().decode() or e.reason)
def api_get(path):
url = base_url + path
req = urllib.request.Request(url)
import base64
req.add_header("Authorization", "Basic " + base64.b64encode(f"{user}:{pw}".encode()).decode())
with urllib.request.urlopen(req, timeout=30) as resp:
return resp.status, resp.read().decode()
# [1] Altes Script mit gleichem Namen entfernen
print(f"Suche ggf. altes Script '{script_name}' ...")
status, body = api_get("/system/script")
if status == 200:
try:
for item in json.loads(body):
if item.get("name") == script_name:
print(f"Entferne altes Script id={item.get('.id')} ...")
api("/system/script/remove", {"id": item[".id"]})
break
except Exception:
pass
# [2] Script anlegen
print(f"Erstelle Script '{script_name}' ({len(source)} Zeichen) ...")
status, body = api("/system/script/add", {"name": script_name, "source": source})
print(f"add -> HTTP {status}: {body[:400]}")
if status >= 400:
sys.exit("Script konnte nicht angelegt werden.")
try:
created = json.loads(body)
except json.JSONDecodeError:
created = {}
script_id = created.get(".id")
# [3] Script ausführen
if script_id:
print(f"Starte Script id={script_id} ...")
status, body = api("/system/script/run", {"id": script_id})
print(f"run -> HTTP {status}: {body[:2000]}")
else:
# Try to find by name
print("Suche Script-ID per Name ...")
status, body = api_get(f"/system/script?name={script_name}")
print(f"getall -> HTTP {status}: {body[:800]}")
print("OK: REST-Aufrufe abgeschlossen")

79
routeros/restore-core.py Normal file
View File

@@ -0,0 +1,79 @@
#!/usr/bin/env python3
"""Stelle das crs326-core Backup vom Cutover-Stand wieder her.
Creds aus ~/.claude.json (mikromcp-env, CRS326_CORE_*).
Backup-Datei wird per SCP hochgeladen und /system backup load ausgefuehrt.
"""
import json
import pathlib
import subprocess
import sys
CORE_IP = "192.168.88.1"
BACKUP = "crs326-core-pre-cutover-20260806-073712.backup"
CLAUDE_JSON = pathlib.Path.home() / ".claude.json"
OUT_DIR = pathlib.Path(__file__).resolve().parent
def load_core_creds():
cfg = json.loads(CLAUDE_JSON.read_text())
env = cfg.get("mcpServers", {}).get("mikromcp", {}).get("env", {})
user = env.get("CRS326_CORE_USER") or env.get("ROUTER_USER")
pw = env.get("CRS326_CORE_PASS") or env.get("ROUTER_PASS")
if not user or not pw:
raise SystemExit("CRS326_CORE_USER/PASS nicht in ~/.claude.json mikromcp.env gefunden")
return user, pw
def ssh_cmd(user, pw, remote_command):
return [
"sshpass", "-p", pw,
"ssh",
"-o", "StrictHostKeyChecking=no",
"-o", "UserKnownHostsFile=/dev/null",
"-o", "PubkeyAuthentication=no",
f"{user}@{CORE_IP}",
remote_command,
]
def scp_cmd(user, pw, local_file, remote_file):
return [
"sshpass", "-p", pw,
"scp",
"-o", "StrictHostKeyChecking=no",
"-o", "UserKnownHostsFile=/dev/null",
"-o", "PubkeyAuthentication=no",
str(local_file),
f"{user}@{CORE_IP}:{remote_file}",
]
def run_silent(cmd, pw):
r = subprocess.run(cmd, capture_output=True, text=True)
if r.returncode != 0:
out = (r.stdout + r.stderr).replace(pw, "***")
raise SystemExit(f"Befehl fehlgeschlagen (rc={r.returncode}):\n{out}")
return r
def main():
local_backup = OUT_DIR / BACKUP
if not local_backup.exists():
raise SystemExit(f"{local_backup} fehlt")
user, pw = load_core_creds()
print(f"crs326-core Restore via SSH {user}@{CORE_IP}")
print(f" Lade {BACKUP} hoch ...")
run_silent(scp_cmd(user, pw, local_backup, BACKUP), pw)
print(f" Spiele Backup zurueck ...")
run_silent(ssh_cmd(user, pw, f'/system backup load name="{BACKUP}" password=""'), pw)
print(" Backup geladen, Router rebootet.")
print(" Warte auf Wiederverbindung an 10.40.41.211 ...")
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,36 @@
# TP15 — Bereinigt den halb-importierten Zustand auf crs326-core,
# damit die korrigierte Cutover-Config sauber neu importiert werden kann.
# Alle entfernten Objekte werden vom korrigierten tp15-core-cutover-intended.rsc
# wieder angelegt (disabled=yes).
:log info "TP15-cleanup: start"
# [1] Interface-Lists (incl. Members werden kaskadiert entfernt)
:if ([:len [/interface/list/find name="WAN"]] > 0) do={
/interface/list/remove [find name="WAN"]
}
:if ([:len [/interface/list/find name="LAN"]] > 0) do={
/interface/list/remove [find name="LAN"]
}
# [2] Staged GW-IPs
:if ([:len [/ip/address/find comment~"L3 SVI VLAN"]] > 0) do={
/ip/address/remove [find comment~"L3 SVI VLAN"]
}
# [3] Staged WAN DHCP-Client
:if ([:len [/ip/dhcp-client/find comment~"TP15 WAN"]] > 0) do={
/ip/dhcp-client/remove [find comment~"TP15 WAN"]
}
# [4] DHCP-Pools (keine Server referenzieren sie, Import blieb vor dhcp-server stehen)
:if ([:len [/ip/pool/find name~"^pool-v"]] > 0) do={
/ip/pool/remove [find name~"^pool-v"]
}
# [5] DHCP-Server/Network-Einträge
:if ([:len [/ip/dhcp-server/network/find address~"^10\\.40\\."]] > 0) do={
/ip/dhcp-server/network/remove [find address~"^10\\.40\\."]
}
:log info "TP15-cleanup: done"