From d281a0f36f69c02cfdeb6451981903b8446ae731 Mon Sep 17 00:00:00 2001 From: Christoph Emsenhuber Date: Fri, 7 Aug 2026 22:39:29 +0200 Subject: [PATCH] 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 --- .gitignore | 2 + PLAN.md | 81 +++ docs/teilprojekte.md | 13 +- mcp/mikromcp/config/routers.yaml | 10 +- nautobot/ingest-crs326-core-l3.py | 153 +++++ nautobot/mikrotik_routeros.j2 | 126 ++++ routeros/ap1-cutover-summary.txt | 10 + routeros/ap1-leases-to-core.rsc | 59 ++ .../ap1-pre-core-cutover-20260806-041627.rsc | 558 ++++++++++++++++++ .../ap1-pre-core-cutover-20260806-073616.rsc | 558 ++++++++++++++++++ routeros/apply-core-cutover-l3.py | 75 +++ routeros/backup-ap1.py | 91 +++ routeros/backup-core.py | 86 +++ routeros/convert-ap1-leases.py | 59 ++ ...rs326-core-pre-cutover-20260806-041719.rsc | 92 +++ ...core-pre-cutover-20260806-073712-fixed.rsc | 314 ++++++++++ ...rs326-core-pre-cutover-20260806-073712.rsc | 360 +++++++++++ routeros/export-ap1-cutover-data.py | 119 ++++ routeros/fetch-core-cutover-config.py | 51 ++ routeros/fix-core-rsc.py | 37 ++ routeros/import-core-rsc.py | 96 +++ routeros/push-gc-template.py | 59 ++ routeros/rest-core-script.py | 99 ++++ routeros/restore-core.py | 79 +++ routeros/tp15-core-cleanup-partial.rsc | 36 ++ 25 files changed, 3214 insertions(+), 9 deletions(-) create mode 100644 PLAN.md create mode 100644 nautobot/ingest-crs326-core-l3.py create mode 100644 nautobot/mikrotik_routeros.j2 create mode 100644 routeros/ap1-cutover-summary.txt create mode 100644 routeros/ap1-leases-to-core.rsc create mode 100644 routeros/ap1-pre-core-cutover-20260806-041627.rsc create mode 100644 routeros/ap1-pre-core-cutover-20260806-073616.rsc create mode 100644 routeros/apply-core-cutover-l3.py create mode 100644 routeros/backup-ap1.py create mode 100644 routeros/backup-core.py create mode 100644 routeros/convert-ap1-leases.py create mode 100644 routeros/crs326-core-pre-cutover-20260806-041719.rsc create mode 100644 routeros/crs326-core-pre-cutover-20260806-073712-fixed.rsc create mode 100644 routeros/crs326-core-pre-cutover-20260806-073712.rsc create mode 100644 routeros/export-ap1-cutover-data.py create mode 100644 routeros/fetch-core-cutover-config.py create mode 100644 routeros/fix-core-rsc.py create mode 100644 routeros/import-core-rsc.py create mode 100644 routeros/push-gc-template.py create mode 100644 routeros/rest-core-script.py create mode 100644 routeros/restore-core.py create mode 100644 routeros/tp15-core-cleanup-partial.rsc diff --git a/.gitignore b/.gitignore index a4dba85..aca543d 100644 --- a/.gitignore +++ b/.gitignore @@ -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 diff --git a/PLAN.md b/PLAN.md new file mode 100644 index 0000000..5d4227e --- /dev/null +++ b/PLAN.md @@ -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. diff --git a/docs/teilprojekte.md b/docs/teilprojekte.md index 96fc49b..5bcd57f 100644 --- a/docs/teilprojekte.md +++ b/docs/teilprojekte.md @@ -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`) diff --git a/mcp/mikromcp/config/routers.yaml b/mcp/mikromcp/config/routers.yaml index 260f4d0..5ad2af6 100644 --- a/mcp/mikromcp/config/routers.yaml +++ b/mcp/mikromcp/config/routers.yaml @@ -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" diff --git a/nautobot/ingest-crs326-core-l3.py b/nautobot/ingest-crs326-core-l3.py new file mode 100644 index 0000000..7e4e2fc --- /dev/null +++ b/nautobot/ingest-crs326-core-l3.py @@ -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..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.'}") diff --git a/nautobot/mikrotik_routeros.j2 b/nautobot/mikrotik_routeros.j2 new file mode 100644 index 0000000..2fb2ca7 --- /dev/null +++ b/nautobot/mikrotik_routeros.j2 @@ -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 %} diff --git a/routeros/ap1-cutover-summary.txt b/routeros/ap1-cutover-summary.txt new file mode 100644 index 0000000..7dd54e7 --- /dev/null +++ b/routeros/ap1-cutover-summary.txt @@ -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. diff --git a/routeros/ap1-leases-to-core.rsc b/routeros/ap1-leases-to-core.rsc new file mode 100644 index 0000000..8c2daf1 --- /dev/null +++ b/routeros/ap1-leases-to-core.rsc @@ -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"] diff --git a/routeros/ap1-pre-core-cutover-20260806-041627.rsc b/routeros/ap1-pre-core-cutover-20260806-041627.rsc new file mode 100644 index 0000000..10349bf --- /dev/null +++ b/routeros/ap1-pre-core-cutover-20260806-041627.rsc @@ -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 diff --git a/routeros/ap1-pre-core-cutover-20260806-073616.rsc b/routeros/ap1-pre-core-cutover-20260806-073616.rsc new file mode 100644 index 0000000..a788869 --- /dev/null +++ b/routeros/ap1-pre-core-cutover-20260806-073616.rsc @@ -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 diff --git a/routeros/apply-core-cutover-l3.py b/routeros/apply-core-cutover-l3.py new file mode 100644 index 0000000..323e1f2 --- /dev/null +++ b/routeros/apply-core-cutover-l3.py @@ -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() diff --git a/routeros/backup-ap1.py b/routeros/backup-ap1.py new file mode 100644 index 0000000..5f64831 --- /dev/null +++ b/routeros/backup-ap1.py @@ -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() diff --git a/routeros/backup-core.py b/routeros/backup-core.py new file mode 100644 index 0000000..c3396c0 --- /dev/null +++ b/routeros/backup-core.py @@ -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() diff --git a/routeros/convert-ap1-leases.py b/routeros/convert-ap1-leases.py new file mode 100644 index 0000000..7f58d2b --- /dev/null +++ b/routeros/convert-ap1-leases.py @@ -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() diff --git a/routeros/crs326-core-pre-cutover-20260806-041719.rsc b/routeros/crs326-core-pre-cutover-20260806-041719.rsc new file mode 100644 index 0000000..ccb9d03 --- /dev/null +++ b/routeros/crs326-core-pre-cutover-20260806-041719.rsc @@ -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 diff --git a/routeros/crs326-core-pre-cutover-20260806-073712-fixed.rsc b/routeros/crs326-core-pre-cutover-20260806-073712-fixed.rsc new file mode 100644 index 0000000..7734720 --- /dev/null +++ b/routeros/crs326-core-pre-cutover-20260806-073712-fixed.rsc @@ -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 diff --git a/routeros/crs326-core-pre-cutover-20260806-073712.rsc b/routeros/crs326-core-pre-cutover-20260806-073712.rsc new file mode 100644 index 0000000..31633b6 --- /dev/null +++ b/routeros/crs326-core-pre-cutover-20260806-073712.rsc @@ -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 diff --git a/routeros/export-ap1-cutover-data.py b/routeros/export-ap1-cutover-data.py new file mode 100644 index 0000000..5ea5601 --- /dev/null +++ b/routeros/export-ap1-cutover-data.py @@ -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 != "<<>>": + 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() diff --git a/routeros/fetch-core-cutover-config.py b/routeros/fetch-core-cutover-config.py new file mode 100644 index 0000000..afa9361 --- /dev/null +++ b/routeros/fetch-core-cutover-config.py @@ -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() diff --git a/routeros/fix-core-rsc.py b/routeros/fix-core-rsc.py new file mode 100644 index 0000000..2261567 --- /dev/null +++ b/routeros/fix-core-rsc.py @@ -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) diff --git a/routeros/import-core-rsc.py b/routeros/import-core-rsc.py new file mode 100644 index 0000000..7f757a8 --- /dev/null +++ b/routeros/import-core-rsc.py @@ -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 [--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}") diff --git a/routeros/push-gc-template.py b/routeros/push-gc-template.py new file mode 100644 index 0000000..0e8d10b --- /dev/null +++ b/routeros/push-gc-template.py @@ -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() diff --git a/routeros/rest-core-script.py b/routeros/rest-core-script.py new file mode 100644 index 0000000..cb55fa5 --- /dev/null +++ b/routeros/rest-core-script.py @@ -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 [--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") diff --git a/routeros/restore-core.py b/routeros/restore-core.py new file mode 100644 index 0000000..fe9ae91 --- /dev/null +++ b/routeros/restore-core.py @@ -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() diff --git a/routeros/tp15-core-cleanup-partial.rsc b/routeros/tp15-core-cleanup-partial.rsc new file mode 100644 index 0000000..2f93a57 --- /dev/null +++ b/routeros/tp15-core-cleanup-partial.rsc @@ -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"