Initial import from bf-network-r6.opencode — opencode migration

This commit is contained in:
2026-08-26 12:44:21 +02:00
commit 5ee0716dda
297 changed files with 72324 additions and 0 deletions

63
routeros/HANDOVER-wifi.md Normal file
View File

@@ -0,0 +1,63 @@
# Übergabe-Prompt: hAP ax² WLAN/CAPsMAN-Setup (BF)
```
Du übernimmst die WLAN-Einrichtung von zwei MikroTik hAP ax². ARBEITE DOKU-GEGRÜNDET
(MikroTik RouterOS 7 "wifi"/CAPsMAN, help.mikrotik.com) — KEINE Trial-and-Error-Schleifen.
Lies ZUERST die Doku zu: /interface/wifi, CAPsMAN, configuration, datapath, security,
channel, provisioning. Verstehe das "bound"-Konzept, BEVOR du etwas änderst.
ZIEL
- ap1 = CAPsMAN-Manager + AP, ap2 = CAP (zieht Config von ap1).
- Erst ein TEST-WLAN auf dem aktuellen FLAT-Netz (Bridge, KEIN VLAN), SSID "BF-Main"
(Name egal). Verifiziere: Client verbindet, Roaming ap1<->ap2.
- Bänder: wifi1 = 5 GHz (5ghz-ax), wifi2 = 2,4 GHz (2ghz-ax) — verifiziert.
- WPA3-only für BF-Main ist gewünscht/ok. Country = Austria. Temp-Passphrase (User
setzt echte später), z.B. "BFtest-2026-tmp".
ZUGANG
- ap1 = 10.40.41.1, ap2 = 10.40.41.2. user=admin pw=46484b9952#P
- REST über HTTP funktioniert: curl -s -u 'admin:46484b9952#P' http://10.40.41.1/rest/interface/wifi
ACHTUNG: nested-set via REST ({"channel":{"band":..}} bzw. "channel.band") hat beim
Vorgänger NICHT zuverlässig gegriffen (kam als None zurück). Lieber SSH/CLI ODER
korrekten REST-Pfad aus der Doku verifizieren.
- SSH geht (1. Login fragt "software license? [Y/n]" -> n). Tools: telnet/expect (macOS).
Python 3.14 hat KEIN telnetlib.
STAND
- Beide auf RouterOS 7.23.1 + RouterBOOT 7.23.1, Identity ap1/ap2, Mgmt-IP .1/.2 auf der
Bridge (untagged vlan1). Default-Bridge (ether2-5 + wifi). ether1=WAN ungenutzt. Uplink=ether2.
DAS PROBLEM (Kern!)
- Vorgänger hat CAPsMAN halb aufgesetzt (auf ap1: security/datapath/configuration/
provisioning; auf ap2: cap). Kopplung lief (ap2 bei ap1 registriert, state=Ok).
- ABER die wifi-Interfaces sind "bound=true" (CAPsMAN-gebunden) -> lokale
`set channel.band/configuration.*` werden IGNORIERT -> channel=None, running=false,
es funkt NICHTS. Auch nach `capsman/cap disable` + `provisioning remove` blieb bound=true.
- VERMUTUNG (DOKU PRÜFEN!): Im CAPsMAN-Modell gehört Band/Kanal in die configuration bzw.
in ein /interface/wifi/channel-Profil + provisioning mit supported-bands (je Radio das
passende Config). cfg-test hatte KEINEN Kanal -> gebundene Interfaces ohne Kanal -> aus.
Klär aus der Doku: wie löst man "bound" sauber (ggf. komplettes Reset der wifi-Config,
ggf. Reboot/Power-Cycle), und wie setzt man Band/Kanal im CAPsMAN-Modell korrekt.
ZU TUN
1. wifi-Config sauber entwirren (bound lösen). Sauberer Reset ODER korrekt über CAPsMAN.
2. Test-WLAN BF-Main hochbringen (beide Bänder, beide APs, gleiche SSID), Client testen,
Roaming testen. (Standalone-inline ist erlaubt, falls CAPsMAN für den Schnelltest zickt
— User will aber eigentlich CAPsMAN: ap2 zieht von ap1.)
3. ERST danach SSID->VLAN (datapath vlan-id) — nutzbar aber erst NACH Router-Cutover.
CONSTRAINTS / SAFETY
- HEUTE/aktuell NICHTS Riskantes, KEIN Router-Cutover. Nur WLAN-Test.
- APs tragen noch keinen Haushalts-Traffic; das Hauptnetz läuft über die FritzBox (unberührt).
- User ist ein paar Tage außer Haus, arbeitet remote mit. Beide APs an SCHALTBAREN
Steckdosen (Remote-Not-Aus) -> bei riskanten Änderungen IMMER Auto-Rückfall-Timer setzen
(/system scheduler, der nach X Min zurückstellt, außer du bestätigst).
- Mgmt der APs (.1/.2 auf Bridge/vlan1) NICHT aussperren.
KONTEXT/REPO
- ~/dev/bf-network-r6/ : Gesamt-Übergabe HANDOVER.md, VLAN-Plan docs/vlan-plan.md,
geplante (noch NICHT verifiziert korrekte) Configs routeros/ap1-manager.rsc / ap2-cap.rsc,
IP-Plan: .1-10 Router/APs, .11-20 Switches; VLANs 10/20/30/40/90/99/100.
ERWARTUNG: präzise nach Doku, keine Rate-Schleifen. Bei Unklarheit: Doku-Stelle nennen, dann handeln.
```

54
routeros/README.md Normal file
View File

@@ -0,0 +1,54 @@
# hAP ax² Setup — CAPsMAN + VLAN (Design & Plan, REVIEW vor Apply)
Stand: beide auf RouterOS **7.23.1** + RouterBOOT 7.23.1. Namen/IPs gesetzt:
**ap1 = 10.40.41.1** (künftiges Gateway/Router) · **ap2 = 10.40.41.2** (CAP). Default-Bridge (ether2–5+wifi, vlan-filtering aus).
> ⚠️ **Status: NUR PLAN.** An den Geräten bisher nur: Firmware 7.23.1, Identity ap1/ap2, Stray-IP weg, **Mgmt-IP→.1/.2**.
> Exakte 7.23-Syntax (wifi/capsman/provisioning/scheduler) wird **vor Apply gegen das Gerät validiert**.
## Hardware-Realität
- **WiFi 6 (ax)**, dual-band 2,4+5 GHz. **MLO ist NICHT verfügbar** (MLO = WiFi 7/802.11be → bräuchte hAP be³). „MLO wenn möglich" entfällt.
## Rollen
- **ap1 = CAPsMAN-Manager + AP** (später zusätzlich **Router** — L3/DHCP/DNS/NAT/CAKE = **separater Cutover**, NICHT hier).
- **ap2 = CAP** (zieht Wireless-Config von ap1 @ 10.40.41.1).
## ⚠️ Topologie / Verkabelung (Klärungsbedarf)
- **ap1** → **Core-Switch Port 2** (direkt). ✓ Guter Trunk-Kandidat.
- **ap2** → hinter **TP-Link TL-SG108E** → Core-Switch **Port 8**. Damit ap2 die VLANs **getaggt** bekommt, müsste **auch der TP-Link trunken** (kann 802.1Q, aber fummelige UI). **Empfehlung:** ap2 im Wartungsfenster **direkt an einen freien Core-Port** (z.B. Port 3) → sauberer Trunk, keine TP-Link-VLAN-Config.
- **ether1 = WAN — Achtung:** Für **ap1 (Router)** ist ether1 = **WAN zur FB** (Internet); der **LAN-Trunk** zum Core bleibt **ether2** → ap1 braucht **2 Kabel**. Für **ap2 (reiner AP)** gibt es kein WAN; **ein** Uplink (Trunk) genügt. → Den LAN-Trunk auf ether1 zu legen wäre für den Router falsch. **Finale Verkabelung bitte bestätigen.**
## Bridge-VLAN-Layout (beide APs)
- **Uplink ether2 = Trunk:** tagged `10,20,30,40,90,99,100` **+ native/untagged vlan1** (= aktuelles Mgmt-Netz, .1/.2).
- **Mgmt bleibt erreichbar** (untagged vlan1), solange der Switch-Port native vlan1 untagged bleibt.
- `vlan-filtering=yes` zuletzt — mit **Auto-Rollback** (5 min) gegen Lockout.
## WLAN (SSID → VLAN via CAPsMAN-Datapath)
| SSID | VLAN | Security |
|---|---|---|
| `BF-Main` | 10 (MAIN) | **WPA3-only** |
| `BF-IoT` | 30 (IOT) | WPA2/WPA3-PSK |
| `BF-Guest` | 90 (GUEST) | WPA2/WPA3-PSK |
Beide Bänder (2,4+5 GHz ax). **Temp-Passphrase** (Platzhalter `BFtest-2026-tmp`) — User setzt echte in Ruhe.
> VLAN-SSIDs bekommen erst **nach dem Router-Cutover** DHCP/Gateway. Sofort-Test optional via Temp-SSID auf **native vlan1** (FB-DHCP).
## Apply-Reihenfolge (wenn freigegeben)
1. **Switch:** AP-Uplink-Port(s) → Trunk (tagged 10,20,30,40,90,99,100 + native vlan1). [ap2 erst sinnvoll, wenn direkt am Core ODER TP-Link trunkt]
2. **ap1:** Bridge-VLAN → CAPsMAN-Manager → (Auto-Rollback) vlan-filtering=yes.
3. **ap2:** Bridge-VLAN → CAP enable (manager=10.40.41.1).
4. Verifizieren: ap2 im Manager; SSIDs auf beiden Radios; Mgmt erreichbar. Rollback canceln.
## Safety — Auto-Rollback
```
/system scheduler add name=vlan-rollback interval=5m \
on-event="/interface bridge set [find name=bridge] vlan-filtering=no; /system scheduler remove vlan-rollback"
```
## Offene Punkte (User)
- Finale Verkabelung bestätigen (ap1: ether1→FB-WAN, ether2→Core-Trunk; ap2: direkt an Core vs. TP-Link trunken).
- Switch-Mgmt-IP: `.3` oder `.239` (da `.2` jetzt = ap2).
- Echte WLAN-Passphrasen (später).
## Dateien
- `routeros/ap1-manager.rsc` · `routeros/ap2-cap.rsc`

View File

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

File diff suppressed because it is too large Load Diff

View File

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

Binary file not shown.

View File

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

Binary file not shown.

View File

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

View File

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

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

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

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

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

View File

@@ -0,0 +1,58 @@
# BF-FritzBox: Bridge/Passthrough + alte Tunnel abbauen — Klick-Anleitung
> **Methode:** Claude erklärt Absicht + Klickpfad, **du klickst** an der BF-FritzBox (FB 6660, Liwest, `10.40.41.254`).
> ⚠️ FRITZ!OS-Labels sind versionsabhängig — wo unsicher, zählt die **Absicht**; sag mir, was die UI anbietet.
> 🔗 Teil des permanenten Cutovers — Gesamtablauf: `routeros/wan-cutover-runbook.md` + `docs/tp10-vpn-migration.md`.
## Ziel
Die BF-FB gibt die **öffentliche Liwest-IPv4 per Passthrough an ap1-ether1** weiter → **ap1 wird der Edge** (Public-IP,
VPN-Hub). Die FB verliert ihre Router-/VPN-Rolle, **Telefonie bleibt auf der FB** (separater Voice-Pfad, von der
IPv4-Weitergabe unberührt). Mechanismus = **LAN4-Passthrough** (= der reversible Weg aus dem Test, **kein Liwest-Anruf
nötig**). Optionaler späterer Schritt: echter DOCSIS-Bridge/Modem via Liwest-Support (sauberer, aber nicht erforderlich).
## ⚠️ Reihenfolge — Hand in Hand mit ap1
1. **FM-FB** hat die neue WG-`.conf` (bf4-Endpoint) — `fritzbox-wg-lanlan-klickanleitung.md`. DW: laut dir erledigt.
2. **ap1-Apply** läuft (`routeros/tp10-cutover-apply.rsc`) — Hub scharf, WAN-Seal aktiv. **Erst danach** an die BF-FB.
3. **DIESE Anleitung:** Passthrough an + ether1 umstecken. (Tunnel-Teardown unten erst **nach** Verify.)
> Konsole während des Cutovers: **FB-WiFi → ap1 `10.40.41.1`** (WebFig) über ether2. **ether2 NIE ziehen.**
---
## Teil 1 — IPv4 an ap1 durchreichen (LAN4-Passthrough)
`Internet → Zugangsdaten` (bzw. `Internet → ...`) → Funktion **„Gerät mit eigener öffentlicher IPv4" / „Passthrough"
/ „Exposed Host an LAN4"** suchen.
- **Absicht:** Die eine öffentliche Liwest-IPv4 wird **per DHCP an das Gerät an LAN4** (= ap1-ether1) übergeben.
- Aktivieren, LAN4 als Passthrough-Port wählen, speichern.
- Falls FRITZ!OS das nicht direkt anbietet: sag mir die vorhandenen Menüpunkte — dann lotse ich Feld für Feld
(alternativ DOCSIS-Bridge via Liwest-Anruf, dann ist die FB reines Modem).
## Teil 2 — Umstecken
- **ether1** von ap1 an den **FB-LAN4-Passthrough-Port**. **ether2 bleibt stecken** (Uplink/Konsole).
- ap1-ether1 zieht jetzt Lease + **Public-IP**; Default-Route distance=1 übernimmt automatisch (Bridge-Fallback@2),
Egress masquerade-ether1 greift. → Prüfen via `mcp__mikromcp__list_dhcp_clients` (ether1 status=bound) und
`mcp__mikromcp__list_routes` (Default via ether1 aktiv).
## Teil 3 — 🔴 DDNS abgleichen (sonst finden die Spokes den Edge nicht)
- ether1-Public-IP lesen: `mcp__mikromcp__run_command` → `/ip address print where interface=ether1`.
- **`bf4.emsenhuber.net`** (Cloudflare, Zone `emsenhuber.net`, **proxied=false**, TTL 120) auf **genau diese IP** setzen,
falls abweichend. *(ap1 kann device-mode-bedingt nicht selbst aktualisieren → vom Laptop/NAS aus.)*
## Teil 4 — Verify (erst danach Tunnel abbauen!)
- `mcp__mikromcp__list_wireguard_peers` → **peer1 (FM) und peer2 (DW) Handshake**, rx/tx > 0.
- Von einem BF-Host **per IP** (kein NetBIOS/Broadcast): SMB/445 auf je einen FM- und DW-Host.
- **Antwort-Quelle MUSS die echte LAN-IP sein** (z.B. `10.101.0.x` / Geli-LAN), **nicht** `10.255.255.x` (= FB-NAT).
## Teil 5 — Alte AVM-Tunnel abbauen (nach grünem Verify)
`Internet → Freigaben → Reiter „VPN (WireGuard)"`:
- **Alte FM↔BF-FB-WG** **deaktivieren/löschen** — die WG-Kopplung, über die FM **bisher** lief (FM peerte mit der
BF-FB auf `xvbeo64…myfritz:51821`). Jetzt peert FM↔**ap1** (`bf4…:51821`). *(IPsec ist längst gekillt → dort nichts zu tun.)*
- **Alte DW/Geli-FB-WG** (Server-Port **51820**) **deaktivieren/löschen**.
*(DW-Gotcha: Gelis FB lehnt 2 WG zum selben Zielnetz ab → der neue Geli↔ap1-Tunnel ersetzt den alten direkt; der
Swap auf Geli-Seite ist laut dir bereits erfolgt.)*
- Portfreigabe **51821 → 10.40.41.1** wird im Passthrough hinfällig → entfernen.
## Rollback (jederzeit, lockout-sicher)
- **WAN zurück:** Passthrough an der FB aus → **ether1** zurück an einen normalen FB-LAN-Port. ap1 fällt über die
distance-2-Route via bridge automatisch in den Double-NAT zurück. **LAN bleibt durchgehend online** (ether2 unberührt).
- **Tunnel:** alte IPsec/WG erst nach grünem Verify abschalten → bis dahin jederzeit reaktivierbar.
- **ap1-Seite:** per `routeros/tp10-cutover-apply.rsc`-Umkehr (Peers disabled, Seal/Routen entfernen).

View File

@@ -0,0 +1,42 @@
# BF-FritzBox: Statikrouten für die WG-Spoke-Netze (FM/DW → ap1) — Klick-Anleitung
> **Methode:** Claude erklärt Absicht + Klickpfad, **du klickst** an der BF-FritzBox (`10.40.41.254`,
> kein MCP). ⚠️ FRITZ!OS-Labels sind versionsabhängig — Absicht zählt.
>
> 🔗 Teil von TP10 (`docs/tp10-vpn-migration.md`). **Interim-Maßnahme im Double-NAT** — beim permanenten
> FB-Bridge-Cutover ist die FB aus dem Datenpfad → diese Routen werden gegenstandslos und können weg.
## 🎯 ROOT CAUSE (verifiziert 2026-06-28) + der Fix
**Fehlerbild:** WG `peer dw` gesund (Handshake, rx/tx beidseitig), aber **dw → BF antwortet nicht**.
Die scheinbar funktionierende Gegenrichtung lief über **WARP** (out-of-band), nicht über den Tunnel.
**Ursache:** Die BF-Infra hängt auf `10.40.41.0/24` (NAS .10, macmini .167, pve .100, Omada .175/.176 …).
ap1s DHCP auf `bridge` ist **disabled** → diese Hosts beziehen DHCP von der **FritzBox** und haben
**Default-GW `10.40.41.254` (FB)**, nicht ap1. Antwortet ein BF-Host an `10.95.10.x`, schickt er's an die
FB — die hat **keine Route** zu den Spoke-Netzen mehr (terminiert die Tunnel nicht mehr) → **Antwort
verschwindet** → *no response*.
> **DER FIX:** Der FB beibringen, dass die Spoke-Netze **über ap1 (`10.40.41.1`)** erreichbar sind.
> Dann gehen die Antworten der `.41`-Hosts an die FB → FB → ap1 → Tunnel. Voll transparent, kein NAT.
## Eckdaten (beide Routen)
| Netzwerk (Ziel) | Subnetzmaske | Gateway | Spoke |
|---|---|---|---|
| `10.95.10.0` | `255.255.255.0` | `10.40.41.1` (ap1) | **DW/Geli** |
| `10.101.0.0` | `255.255.255.0` | `10.40.41.1` (ap1) | **FM** |
## Klickpfad
**Heimnetz → Netzwerk → Netzwerkeinstellungen → „weitere Einstellungen" → IPv4-Routen → Neue Route.**
Pro Route: Netzwerk + Maske + Gateway aus der Tabelle, **aktiv** setzen, übernehmen. Für beide Zeilen.
## Verify (entscheidend: LAN-Host, **ohne** WARP)
1. Von Geli (`10.95.10.246/.247`, WARP **aus**) → BF-Infra **per IP**: `ping 10.40.41.10`, SMB/445 `10.40.41.10`.
2. Quell-IP am BF-Zielhost = echte `10.95.10.x` (kein `10.255.255.x`, kein `10.40.41.1`) → bestätigt no-NAT.
3. ap1: `list_firewall_rules out-interface=wg-hub` (*F) steigt; `list_connections` zeigt `10.95.10.x ↔ 10.40.41.x` established.
4. FM-Quercheck: von `10.101.0.x` → BF-`.41`-Host ebenfalls grün.
> **Status 2026-06-28:** Beide Routen angelegt und **ohne WARP erfolgreich getestet** →
> dw → BF und fm → BF erreichbar. ✅
## Reversibilität
Routen jederzeit in der FB-UI löschbar. Keine ap1-Änderung. Entfallen automatisch beim Bridge-Cutover.

View File

@@ -0,0 +1,95 @@
# FritzBox WireGuard als LAN-LAN-Kopplung (FM/DW-Spoke → ap1-Hub) — Klick-Anleitung
> **Methode:** Claude erklärt Absicht + Klickpfad, **du klickst** an der FritzBox. FM-Standort
> (`10.101.0.0/24`, GW `10.101.0.254`). **IPsec ist längst gekillt** → FM läuft bereits über **FB-WireGuard**
> (peert aktuell mit der **BF-FB** auf `xvbeo64…myfritz:51821`; FM-FB horcht selbst auf
> `abxhutfgsid9snmy.myfritz.net:52476`). Hub = ap1 (BF). ⚠️ FRITZ!OS-Labels sind versionsabhängig — Absicht zählt.
>
> 🔗 **Teil des permanenten Cutovers** (`docs/tp10-vpn-migration.md` + `routeros/wan-cutover-runbook.md`).
> Der Hub-Endpoint ist jetzt **`bf4.emsenhuber.net:51821`** (DDNS), **nicht** mehr MyFRITZ — denn die BF-FB
> geht in den Bridge-Mode und verliert MyFRITZ + Portfreigabe. `bf4` zeigt direkt auf ap1-ether1 (Public-IP).
## 🎯 ROOT CAUSE (bestätigt 2026-06-16) + der Fix
**Messung:** Ping auf realen FM-Host `10.101.0.10` über WG → Antwort kam aus **`10.255.255.2`** (Tunnel-IP) → **die FB
masqueradet den Tunnel.** Ursache: unsere `.conf` hatte als Interface-`Address` eine **Transfernetz-IP**
(`10.255.255.2/32`) → FRITZ!OS behandelt das als **Einzelgerät** und NAT't.
**AVM-Modell:** FRITZ!OS nutzt für WG-LAN-LAN **kein Transfernetz, sondern Proxy-ARP**. Die WG-Interface-Adresse der
FB **muss ihre eigene LAN-IP** sein → dann routet die FB ihr LAN **ohne NAT**, echte Host-IPs.
> **DER FIX (eine Zeile):** FB-WG-Interface-`Address` = **`10.101.0.254/24`** (FM-LAN-IP), **nicht** `10.255.255.2/32`.
> Transfernetz `10.255.255.0/24` **komplett streichen**.
## Eckdaten (FM; DW analog)
| Feld | Wert FM | Wert DW |
|---|---|---|
| **WG-Interface-Address (FB)** | **`10.101.0.254/24`** (LAN-IP!) | **`10.95.10.1/24`** (LAN-IP! **single**, nach Renumber) |
| Erreichbare Netze / AllowedIPs (→Hub) | `10.40.0.0/16` | `10.40.0.0/16` |
| Hub-Endpoint | **`bf4.emsenhuber.net:51821`** | dito (DW: CGNAT → wählt ausgehend) |
| Hub-Public-Key | `P/bztIk13cAmLHbN6oHGGnWAKLZsqR5TXAbFP+NGGBk=` | dito |
| MTU / Keepalive | `1412` / `25` | `1412` / `25` |
| Vorbereitete `.conf` | `routeros/.secrets/wg-fm.conf` | `routeros/.secrets/wg-dw.conf` |
## ⚠️ Sicherheit (FM ist produktiv!)
- Die **bestehende FM↔BF-FB-WG bleibt an** = Sicherheitsnetz, bis der Echt-Host-Test über ap1 grün ist.
- ap1-Seite kommt per `routeros/tp10-cutover-apply.rsc` (Hub scharf, peer1 mit FM-Endpoint) — dort nichts klicken.
---
## ⚡ Schnellpfad (wahrscheinlich ausreichend)
FM ist **schon** auf WG mit korrektem LAN-IP-Modell (`Address=10.101.0.254/24`) — sonst liefe es nicht sauber.
Dann ist die ganze Migration **ein Feld**: in der bestehenden FM-WG-Verbindung den **Peer-Endpoint** von
`xvbeo64…myfritz.net:51821` (BF-FB) auf **`bf4.emsenhuber.net:51821`** (ap1) umbiegen — fertig.
Erlaubt FRITZ!OS kein Bearbeiten des Endpoints → Verbindung **löschen + via `wg-fm.conf` neu importieren**
(gleicher Key/Address, nur neuer Endpoint). **Nur wenn FM noch die alte Transfernetz-Adresse hat**, brauchst du
Teil 1+2 unten.
## Teil 1 — Bestehende (falsche, transfernetz-basierte) FM-WG-Verbindung entfernen
*(Nur falls FM noch NICHT auf dem LAN-IP-Modell ist.)*
`Internet → Freigaben → Reiter „VPN (WireGuard)"` → die FM-Verbindung **löschen** (sie hat die falsche
Transfernetz-Adresse → NAT).
## Teil 2 — Neu als LAN-LAN-Kopplung anlegen
`„Verbindung hinzufügen"` → Typ **„Netzwerke koppeln" / „LAN-LAN"** wählen (**nicht** „Einzelnes Gerät").
- Falls FRITZ!OS dort nur den **FB↔FB**-Assistenten anbietet: stattdessen **benutzerdefinierte/importierte Config**
und **`routeros/.secrets/wg-fm.conf`** importieren — die hat jetzt die korrekte `Address = 10.101.0.254/24`.
- **Kernpunkt:** Die WG-Interface-Adresse dieser Verbindung muss **`10.101.0.254/24`** sein (die FB-LAN-IP),
erreichbares Netz Richtung Hub = **`10.40.0.0/16`**. **„Gesamten Internetverkehr über VPN" = AUS.**
- ⚠️ Falls der Import eine LAN-IP als Interface-Adresse **ablehnt** (Überlapp mit dem LAN): dann den AVM-Wizard
„Netzwerke koppeln" nutzen und dort die **lokale FB-IP** als Verbindungs-IP eintragen (das ist genau der
Proxy-ARP-Weg). Sag mir, was die UI anbietet — dann lotse ich Feld für Feld.
- **Public-Key** der Verbindung notieren → mir geben, falls FRITZ!OS einen **eigenen** Key erzeugt
(dann gleiche ich `peer1` auf ap1 ab; aktuell steht dort der Key aus unserer `.conf`).
## Teil 3 — ap1-Abgleich (mache ich, falls nötig)
- Falls FB neuen Key erzeugt: `peer1`-Public-Key aktualisieren.
- peer1 allowed-address bleibt `10.101.0.0/24` (Transfernetz-/32 ist obsolet, schadet aber nicht).
> Sag Bescheid — nicht-disruptiv per MikroMCP.
## Teil 4 — Verifizieren, dann erst die alte FM↔BF-FB-WG abbauen
1. Neuer Tunnel aktiv (alte FM↔BF-FB-WG noch an lassen). Ich pinge von ap1 sourced `10.40.20.1` → **`10.101.0.10`**
+ `torch wg-hub`:
- **Quelle = `10.101.0.10`** → ✅ kein NAT, echtes LAN-LAN → weiter.
- Quelle = `10.255.255.x` → noch NAT → Adresse/Modus prüfen (Teil 2).
2. Gegenrichtung: FM-Host → BF-Host (NAS) per IP; **SMB/445 per IP** (NetBIOS-Lehre: per IP/DNS, kein Broadcast).
3. Beide Richtungen grün → **alte FM↔BF-FB-WG auf der BF-FB deaktivieren** → erneut testen.
4. Stabil → FM migriert. **DW** läuft analog (CGNAT → ausgehend/Keepalive), aber Single-Address — siehe unten.
## DW-Spezial: Renumber auf 10.95.10.0/24 + Single-Address (LEHRE 2026-06-25)
**Symptom war:** Tunnel stand (Handshake ✓, rx/tx ✓), aber **BF→Geli-Host per IP ging nicht**; ap1-Ping auf
`192.168.178.1` **und** `10.95.10.1` kam immer aus `10.95.10.1` zurück.
**Ursache:** Die WG-`Address` war **Dual** (`192.168.178.1/24, 10.95.10.1/24`). FRITZ!OS macht daraus
`10.95.10.1` als **Primäradresse** und koppelt das echte LAN (`192.168.178.0/24`) **nicht** mehr → kein Host-Routing.
**Regel:** WG-`Address` = **GENAU EINE** Adresse = die **aktuelle** FB-LAN-IP. Kein Dual, kein Vorab-Netz.
**Vorgehen bei Geli (vor Cutoff):**
1. **FB-LAN renumbern:** `Heimnetz → Netzwerk → Netzwerkeinstellungen → IPv4-Adressen` → FB-LAN-IP **`10.95.10.1`**,
Maske `255.255.255.0`, DHCP-Range `10.95.10.x`. (Geli-Geräte ziehen neue Leases.)
2. **WG-`Address`** der ap1-Verbindung auf **`10.95.10.1/24`** (single) setzen — das `192.168.178.1/24` raus.
3. ap1-Seite ist schon fertig: `peer dw` allowed-address = `10.95.10.0/24`, Route `10.95.10.0/24 → wg-hub`.
4. **Verify:** ap1-Ping (src `10.40.20.1`) auf einen **echten** Geli-Host `10.95.10.x` → Antwort aus **derselben** IP
(nicht aus `.1`, nicht aus `10.255.255.x`). Dann BF↔Geli per IP/SMB testen.
## Rollback
- Auf der FM-FB den Peer-Endpoint zurück auf `xvbeo64…myfritz:51821` (BF-FB) biegen bzw. die alte Verbindung
wieder aktivieren → Ausgangszustand. ap1-Seite ist inert, kann bleiben.

View File

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

View File

@@ -0,0 +1,133 @@
#!/usr/bin/env python3
r"""Push nautobot/mikrotik_routeros.j2 to the bf-golden-config Gitea repo.
Reads the Gitea token from ~/.secrets/credentials.env (GITEA_ACCESS_TOKEN).
python3 routeros/push-gc-template.py --dry-run # Diff gegen Remote zeigen
python3 routeros/push-gc-template.py -m "TP22: AP-Zweig" # pushen
SECRET-GUARD (TP22, 2026-08-13)
-------------------------------
Vor jedem PUT wird der Template-Inhalt gegen dieselben Feld-Regexe geprueft, die die
Nautobot-ConfigReplace-Regeln fuers Backup-Scrubbing verwenden. Findet sich ein Wert,
der NICHT die Maske `"<<REDACTED>>"` traegt, bricht das Skript ab.
Grund: genau dieser Weg hat am 2026-07-22 den WG-Hub-private-key und 6 WLAN-PSKs ins
Gitea-Repo getragen (via Backup-Job, aber dieselbe Zielablage). Das Repo musste am
2026-08-13 neu angelegt werden. Der Guard kostet nichts und verhindert die Wiederholung.
"""
import argparse
import base64
import difflib
import json
import pathlib
import re
import subprocess
import sys
import urllib.error
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"
SRC = pathlib.Path(__file__).resolve().parent.parent / "nautobot" / "mikrotik_routeros.j2"
MASK = '"<<REDACTED>>"'
# Muss mit nautobot/ingest-gc-scrubbing.py:RULES synchron bleiben.
SECRET_FIELDS = ["passphrase", "private-key", "pre-shared-key", "wpa-pre-shared-key", "password", "secret"]
VAL = r'("(?:[^"\\]|\\.)*"|\S+)'
def get_token():
m = re.search(r"^GITEA_ACCESS_TOKEN=(.+)$", TOKEN_PATH.read_text(), re.MULTILINE)
if not m:
raise SystemExit("GITEA_ACCESS_TOKEN not found")
return m.group(1).strip()
def scan_for_secrets(text):
"""Gibt eine Liste von (zeilennr, feld) zurueck, deren Wert nicht maskiert ist."""
bad = []
for lineno, line in enumerate(text.splitlines(), 1):
# Kommentarzeilen sind Doku (nennen die Feldnamen absichtlich), nicht Config.
if line.lstrip().startswith("#"):
continue
for field in SECRET_FIELDS:
for m in re.finditer(rf"(?<![\w-]){re.escape(field)}=" + VAL, line):
if m.group(1) != MASK:
bad.append((lineno, field))
return bad
def api(path, method="GET", payload=None, token=""):
req = urllib.request.Request(
f"{REPO}/{path}",
data=json.dumps(payload).encode() if payload else None,
method=method,
headers={"Authorization": f"token {token}", "Content-Type": "application/json"},
)
with urllib.request.urlopen(req, timeout=30) as resp:
raw = resp.read()
return resp.status, (json.loads(raw) if raw else {})
def main():
p = argparse.ArgumentParser()
p.add_argument("-m", "--message", help="Commit-Message (Default: letzter lokaler Commit-Titel)")
p.add_argument("--dry-run", action="store_true", help="nur Diff gegen Remote zeigen")
args = p.parse_args()
content = SRC.read_text()
# --- Secret-Guard: laeuft IMMER, auch im Dry-Run ---
bad = scan_for_secrets(content)
if bad:
print("!! ABBRUCH - unmaskierte Secrets im Template:")
for lineno, field in bad:
print(f" Zeile {lineno}: {field}= traegt nicht die Maske {MASK}")
sys.exit(1)
print(f" Secret-Guard: ok (alle Secret-Felder tragen {MASK})")
token = get_token()
# Remote-Stand holen (sha optional: nach einer Repo-Neuanlage existiert die Datei ggf. nicht)
sha, remote = None, ""
try:
_, meta = api(f"contents/{FILE}?ref=main", token=token)
sha = meta.get("sha")
remote = base64.b64decode(meta.get("content", "")).decode()
except urllib.error.HTTPError as e:
if e.code != 404:
raise
print(f" Remote: {FILE} existiert noch nicht - wird angelegt")
diff = list(difflib.unified_diff(
remote.splitlines(), content.splitlines(),
fromfile=f"remote/{FILE}", tofile=f"local/{SRC.name}", lineterm="", n=2))
if not diff:
print(" Kein Unterschied zum Remote - nichts zu tun.")
return
print(f" Diff: +{sum(1 for l in diff if l.startswith('+') and not l.startswith('+++'))}"
f" / -{sum(1 for l in diff if l.startswith('-') and not l.startswith('---'))} Zeilen")
if args.dry_run:
print("\n".join(diff))
print("\n DRY-RUN - nichts gepusht.")
return
msg = args.message
if not msg:
try:
msg = subprocess.run(["git", "log", "-1", "--pretty=%s"], cwd=SRC.parent.parent,
capture_output=True, text=True, check=True).stdout.strip()
except Exception:
msg = "Update mikrotik_routeros.j2"
payload = {"content": base64.b64encode(content.encode()).decode(), "message": msg, "branch": "main"}
if sha:
payload["sha"] = sha
status, res = api(f"contents/{FILE}", method="PUT" if sha else "POST", payload=payload, token=token)
print(f" HTTP {status} commit={res.get('commit', {}).get('sha', '?')[:8]} msg={msg!r}")
if __name__ == "__main__":
main()

View File

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

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

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

View File

@@ -0,0 +1,71 @@
# =====================================================================
# TP10 Cutover-Apply (ap1) — WG-Hub scharf + WAN-Input-Seal
# =====================================================================
# Zweck: ap1 in einem Rutsch von "geparkt" auf "Edge + VPN-Hub" bringen.
# WANN ANWENDEN: am Bridge-Tag, SCHRITT 2 des Runbooks — VOR dem Umstecken
# von ether1 an den FB-Passthrough-Port.
# SICHERHEIT: im Double-NAT harmlos — Peers warten auf die Spokes, der
# WAN-Drop trifft nur das (noch tote) ether1. Kein LAN-Lockout
# (Drop greift ausschliesslich in-interface-list=WAN = ether1).
# IDEMPOTENT: alle Blöcke sind wiederholbar (find-Guards / set).
# ROLLBACK: Peers disabled=yes, WG-Routen + TP10-seal-* Regeln entfernen.
#
# Anwenden via MikroMCP: upload_file -> run_script (oder /import)
# Vorab IMMER: export_config / create_backup
# ---------------------------------------------------------------------
:log info "TP10-cutover: apply start"
# --- A) WireGuard-Hub: Peers mit korrekten Keys + Allowed-IPs (self-healing)
# fm: erreichbar (eigener myfritz-Endpoint, KEIN CGNAT) -> bidirektional.
# dw: CGNAT -> Dial-in (Endpoint leer). NUR 10.95.10.0/24 (Gelis FB sourct daraus;
# Dual-Address 192.168.178.1/24,10.95.10.1/24 auf der FB verhindert LAN-Kopplung).
/interface/wireguard/peers
set [find where name="fm"] public-key="dzRY0lnnUygijChWe5wXZmXPeNcbdALpqBptA3fyiVE=" \
allowed-address=10.101.0.0/24 \
endpoint-address=abxhutfgsid9snmy.myfritz.net endpoint-port=52476 disabled=no
set [find where name="dw"] public-key="FqYZLztQ/qME5VDie5QLGH++5299WJiE1dPpQnPbzUw=" \
allowed-address=10.95.10.0/24 disabled=no
# --- B) Routen zu den Spoke-Netzen (Gateway = Interface wg-hub)
# RouterOS leitet aus allowed-address KEINE Routen ab -> explizit.
/ip/route
:if ([:len [find where dst-address="10.101.0.0/24" gateway="wg-hub"]]=0) do={ \
add dst-address=10.101.0.0/24 gateway=wg-hub comment="TP10: FM spoke -> wg-hub" }
:if ([:len [find where dst-address="10.95.10.0/24" gateway="wg-hub"]]=0) do={ \
add dst-address=10.95.10.0/24 gateway=wg-hub comment="TP10: DW spoke -> wg-hub" }
# --- C) Input-Chain abdichten (WAN-Seal). Reihenfolge oben->unten:
# est/rel | drop invalid | [WG 51821 vorhanden] | dhcp68 | icmp | LAN | drop WAN
/ip/firewall/filter
# 1+2 ueber die bestehende WG-Regel schieben (est zuerst, dann invalid)
:if ([:len [find where comment="TP10-seal-est"]]=0) do={ \
add chain=input action=accept connection-state=established,related,untracked \
comment="TP10-seal-est" place-before=[find where comment="TP10: WG hub listen (51821)"] }
:if ([:len [find where comment="TP10-seal-inv"]]=0) do={ \
add chain=input action=drop connection-state=invalid \
comment="TP10-seal-inv" place-before=[find where comment="TP10: WG hub listen (51821)"] }
# 4-7 NACH der WG-Regel anhaengen (in dieser Reihenfolge)
:if ([:len [find where comment="TP10-seal-dhcp"]]=0) do={ \
add chain=input action=accept protocol=udp dst-port=68 in-interface-list=WAN \
comment="TP10-seal-dhcp" }
:if ([:len [find where comment="TP10-seal-icmp"]]=0) do={ \
add chain=input action=accept protocol=icmp comment="TP10-seal-icmp" }
:if ([:len [find where comment="TP10-seal-lan"]]=0) do={ \
add chain=input action=accept in-interface-list=LAN comment="TP10-seal-lan" }
:if ([:len [find where comment="TP10-seal-wandrop"]]=0) do={ \
add chain=input action=drop in-interface-list=WAN comment="TP10-seal-wandrop" }
# --- D) ether1-DHCP-Client: kein ISP-DNS-Leak (DoH bleibt einzige Quelle)
# default-route-distance=1 ist bereits gesetzt (Auto-Switchover < Bridge-Fallback@2)
/ip/dhcp-client
set [find where interface="ether1"] use-peer-dns=no
:log info "TP10-cutover: apply done"
# =====================================================================
# NICHT Teil dieses Skripts (bereits korrekt auf ap1):
# - NAT: masquerade out-interface=ether1 (*3) + no-NAT 10.40.0.0/16 (*4)
# - Forward-FW: accept in/out-interface=wg-hub (*E/*F)
# - Default-Fallback-Route 0.0.0.0/0 via 10.40.41.254 distance=2
# VPN-Traffic egresst ueber wg-hub -> matcht keine Masquerade -> echte LAN-IPs.
# =====================================================================

View File

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

View File

@@ -0,0 +1,176 @@
# =====================================================================================
# TP15 — crs326-core L3-Umzug: STAGED CONFIG (ap1 -> core)
# =====================================================================================
# Zweck: core zum L3-Router machen (ersetzt ap1). ALLES was den Betrieb umlenkt, wird
# STAGED (disabled=yes) angelegt -> kein IP-Konflikt mit dem noch aktiven ap1.
# Im Cutover-Fenster werden die disabled-Objekte in EINEM Rutsch aktiviert.
#
# NICHT HEUTE ausführen. Vorbereitungs-Artefakt für ein späteres Wartungsfenster.
# Governance: braucht die zu Fensterbeginn neu zu erteilende Direkt-Ausnahme #3.
# Ausführung: REST (/system/script PUT+run gegen http://10.40.41.211/rest) ODER
# MikroMCP plan_changes->apply_plan (nach MCP-Neustart, ENV CRS326_CORE_* live).
#
# Vorlage = ap1s Live-Config, gelesen 2026-07-23 (MikroMCP). Bridge/VLAN-Filtering/Trunks
# stehen auf core bereits (Bring-up) — hier kommt NUR die L3-Ebene dazu.
#
# ⚠️ ZWEI Bulk-Blöcke bewusst NICHT hier hartkodiert (Transkriptionsfehler-Risiko bei ~50
# MACs) -> zum Fensterbeginn MASCHINELL von ap1 heben, s. Abschnitt [RESERVATIONS].
# ⚠️ WG-Hub PRIVATE KEY ist ein Secret -> NICHT im Repo. Platzhalter unten, am Gerät setzen.
# =====================================================================================
# ============================ [1] INTERFACE-LISTS ====================================
# Voraussetzung für WAN-Seal (input) und NAT (out-interface-list=WAN).
/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
# bridge/VLAN1 bewusst NICHT in LAN (VLAN1 raus). vlan99-Mgmt .11 bleibt vorerst separat.
# ============================ [2] GW-IPs je VLAN (STAGED) ============================
# disabled=yes -> erst im Cutover enable (sonst Duplicate-IP mit ap1).
/ip/address add address=10.40.10.1/24 interface=vlan10 comment="MAIN GW" disabled=yes
/ip/address add address=10.40.20.1/24 interface=vlan20 comment="INFRA GW" disabled=yes
/ip/address add address=10.40.30.1/24 interface=vlan30 comment="IOT GW" disabled=yes
/ip/address add address=10.40.40.1/24 interface=vlan40 comment="MEDIA GW" disabled=yes
/ip/address add address=10.40.50.1/24 interface=vlan50 comment="UNTRUSTED GW" disabled=yes
/ip/address add address=10.40.90.1/24 interface=vlan90 comment="GUEST GW" disabled=yes
/ip/address add address=10.40.99.1/24 interface=vlan99 comment="MGMT GW" disabled=yes
# KEIN 10.40.41.x-GW (VLAN1 raus). KEIN Secondary .254 (FB-Legacy, obsolet).
# Reconcile am Gerät: Bring-up-Mgmt 10.40.99.11 vs. GW .1 auf demselben vlan99
# -> .1 ist der Router; .11 als Secondary behalten oder droppen (Fenster-Entscheid).
# ============================ [3] WAN DHCP-Client (STAGED) ===========================
/ip/dhcp-client add interface=vlan100 use-peer-dns=no add-default-route=yes \
comment="TP15 WAN Liwest (Arris)" disabled=yes
# Liwest ist NICHT MAC-gelockt (Public-IP kam mit eigener MAC, s. wan-macclone-test.rsc)
# -> core zieht am WAN evtl. eine ANDERE Public-IP. Konsequenz für WG-Hub s. [7]/[8].
# ============================ [4] DHCP-Server je VLAN (STAGED) =======================
/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
# ⚠️ pool-v30 = .100-.250 (Shelly-Reservierungen liegen in-pool, s. TP17). Andere .100-.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
# ============================ [RESERVATIONS] — MASCHINELL VON ap1 HEBEN ==============
# ~50 statische Leases (26 Shelly, Sonos, NAS .20.10, Nuki, Withings, Sonoff, ccu,
# Airfryer/Mac mit dhcp-option "dns 1.1.1.1", P750W, Ender, DTUBI, tv, ...).
# NICHT abtippen -> am Fensterbeginn auf ap1:
# /ip/dhcp-server/lease/export file=ap1-leases
# dann: nur dynamic=no übernehmen, server-Namen matchen (dhcp-v10..v99), als disabled=yes
# anlegen; im Cutover mit den Servern zusammen enable. Danach GraphQL/HA-Cross-Check.
# (Alternativ MikroMCP manage_dhcp_lease je Eintrag aus dem Live-Read.)
# ============================ [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
# bridge/VLAN1 NICHT mehr in mdns-repeat (war auf ap1 noch drin, entfällt mit VLAN1).
# Statische A-Records unter bf.lan (11 von ap1; sw1/sw2 RAUS = werden abgebaut):
/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 (Mgmt-IP im Fenster festlegen)"
/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
# ⚠️ ap1/ap2/branch-Mgmt-IPs sind Fenster-Entscheide (Renumber). Oben = Zielannahme.
# ============================ [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 =================================
# Reihenfolge = ap1-Live (order-sensitiv!). Gegen ap1-Export im Fenster gegenprüfen.
# --- forward ---
/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"
# --- input (TP10 WAN-Seal) ---
/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"
# Hinweis: ipsec-passthrough-Regeln (defconf) auf ap1 vorhanden aber 0 Pakete -> weggelassen.
# ============================ [8] WG-HUB (TP10 Multi-Site) ========================
# ⚠️ PRIVATE KEY = ap1s wg-hub-Privkey (SECRET, nicht im Repo). Im Fenster von ap1 holen:
# /interface/wireguard/export ODER :put [/interface/wireguard get wg-hub private-key]
# und HIER einsetzen -> core hält denselben Public Key -> FM/DW brauchen KEINE Reconfig.
/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); kein IP aufs IF (Routing via AllowedIPs)"
/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"
# ✅ GEKLÄRT (2026-07-23): FM+DW adressieren den Hub über `bf4.emsenhuber.net:51821` =
# Cloudflare-A-Record (proxied=false, TTL 120, aktuell 90.146.35.38). KEIN Updater auf
# ap1 (nur WANTEST/mac-clone-Scripts) -> nichts zu migrieren. bf4 folgt der Public-IP
# MANUELL (TP10-Follow-up, nie automatisiert).
# -> Cutover-Konsequenz: zieht core am WAN eine ANDERE Liwest-IP (Lease ist sticky,
# eher nicht), dann via CF_API_TOKEN nachziehen: (a) bf4-A-Record, (b) CF-Access-
# Policy `bf4-home-ip` (90.146.35.38/32). Solange die IP gleich bleibt = No-op.
# -> Chance: core ist NICHT device-mode=home -> scheduler+fetch gehen -> hier den
# bf4-DDNS-Auto-Updater bauen, den ap1 nie konnte (schliesst TP10-Follow-up).
# ============================ [CUTOVER-ENABLE] (im Fenster, EIN Rutsch) ============
# NACH Kabel-Umstecken sw1->core und NACHDEM ap1 als Router still ist:
# /ip/address/enable [find comment~"GW"]
# /ip/dhcp-client/enable [find comment~"TP15 WAN"]
# /ip/dhcp-server/enable [find name~"dhcp-v"]
# (Reservierungen wurden mit angelegt/enabled)
# Dann Verifikation je VLAN (Lease/GW-Ping/Internet), DNS, branch, WG-Handshake, CF/WARP.
# Rollback: hier alles wieder disable + Kabel zurück auf sw1 + ap1 wieder enable.
# =====================================================================================

View File

@@ -0,0 +1,21 @@
# TP19 — VLAN1-Bring-up-Adresse vom Core entfernen.
# Ausgerollt 2026-08-15 ueber Nautobot GC (manual Config Plan), Backup davor:
# core-pre-tp19-vlan1-addr-20260815.backup
#
# `10.40.41.211/24` auf `bridge` (Kommentar TP15-bringup-vlan1) war die letzte
# .41-Spur auf dem Core und der letzte offene Compliance-Drift (ip-address).
# Vorher geprueft: keine DNS-Statics, kein DHCP-Server auf `bridge`, kein Netwatch,
# keine Address-Lists mit .41; `bridge` ist NICHT Mitglied der LAN-Interface-Liste.
# Mgmt laeuft ueber .99.1 (+ Fallback 10.40.10.1), beides unberuehrt.
#
# ERWARTETE NEBENWIRKUNG: die dynamische Connected-Route 10.40.41.0/24 verschwindet
# mit der Adresse. Das ist gewollt — Restverkehr nach VLAN1 scheitert danach sofort
# statt in einen Timeout zu laufen.
#
# Reihenfolge war bindend und ist erfuellt: HA zeigt auf 10.40.20.100 (Integration
# `loaded`), pve hat 10.40.41.100 nicht mehr, Prometheus/ioBroker sind umgezogen.
#
# Selbstprotokollierend — der Deploy-Job zeigt den Geraete-Output nicht (E1028).
:do { /ip address remove [find address="10.40.41.211/24"] } on-error={ :log info "tp19: remove hat geworfen" }
:log info ("tp19: ENDE rest-41-adressen=" . [:tostr [:len [/ip address find address~"10.40.41."]]] . " rest-41-routen=" . [:tostr [:len [/ip route find dst-address~"10.40.41."]]])

View File

@@ -0,0 +1,9 @@
# NTP-Client auf dem AP neu anstossen.
#
# Beide APs zeigen seit jeher auf 10.40.99.1 und standen deshalb dauerhaft auf
# `status: waiting`. Ein Client, der lange erfolglos war, verlaengert seine
# Wartezeit — er merkt also nicht von selbst zeitnah, dass der Core seit
# 2026-08-18 tatsaechlich Zeit liefert. Aus/ein setzt den Zustand zurueck.
:do { /system ntp client set enabled=no } on-error={ :log info "tp19ntp: ap-off warf" }
:do { /system ntp client set enabled=yes servers=10.40.99.1 } on-error={ :log info "tp19ntp: ap-on warf" }
:log info ("tp19ntp: ap-client status=" . [/system ntp client get status])

View File

@@ -0,0 +1,5 @@
# NTP-Client auf crs326-branch nachziehen. Er hatte GAR KEINEN Server
# eingetragen (status=stopped) — die Uhr stimmte nur zufaellig noch.
# Zeitquelle ist der Core (s. routeros/tp19-ntp-core-als-zeitquelle.rsc).
:do { /system ntp client set enabled=yes servers=10.40.99.1 } on-error={ :log info "tp19ntp: branch warf" }
:log info ("tp19ntp: branch status=" . [/system ntp client get status])

View File

@@ -0,0 +1,34 @@
# NTP: den Core zur Zeitquelle des Standorts machen.
#
# BEFUND (2026-08-18, beim TP19-Abschluss gefunden):
# ap1 ntp client -> 10.40.99.1, status=waiting, Uhr zufaellig noch richtig
# ap2 ntp client -> 10.40.99.1, status=waiting, Uhr **17 Stunden nach**
# branch ntp client -> gar keiner, status=stopped
# core /system ntp server = enabled:no
# Alle drei fragen also einen Zeitserver, den es nicht gibt ("waiting" heisst
# genau das). ap2 ist nur der erste, der sichtbar weggelaufen ist — die anderen
# folgen.
#
# WARUM DAS MEHR ALS KOSMETIK IST: Geraete-Logs sind das Hauptwerkzeug in diesem
# Projekt (TP5 lebt vom Kollektor). Beim Pfadbeweis heute nacht hat ap2 einen
# Logeintrag von 04:57 als "2026-08-17 12:19" gestempelt — eine Korrelation
# ueber mehrere Geraete ist damit wertlos.
#
# WEG: der Core hat als einziger eine verlaessliche Zeit (WAN + DoH). Er wird
# Server, die drei anderen bleiben/werden Clients auf 10.40.99.1. Kein Geraet
# braucht dafuer Internet.
#
# ⚠️ Die input-Regel ist Pflicht: seit Etappe 4 endet `input` mit einem Drop.
# Ohne sie waere der NTP-Server eingeschaltet und trotzdem unerreichbar —
# derselbe stille Fehlschlag wie bisher, nur eine Ebene tiefer.
#
# Push: python3 nautobot/gc-manual-plan.py --device crs326-core \
# --commands-file routeros/tp19-ntp-core-als-zeitquelle.rsc \
# --change-id TP19-NTP --deploy \
# --assert '/system/ntp/server|enabled=true'
# ---------------------------------------------------------------------------
:do { /system ntp server set enabled=yes } on-error={ :log info "tp19ntp: 01 server warf" }
:do { :if ([:len [/ip firewall filter find comment="TP5-E4: ntp"]] = 0) do={ /ip firewall filter add chain=input action=accept in-interface-list=LAN protocol=udp dst-port=123 comment="TP5-E4: ntp" place-before=[/ip firewall filter find comment="TP5: input log-only"] } } on-error={ :log info "tp19ntp: 02 fw warf" }
:log info ("tp19ntp: ENDE server-enabled=" . [:tostr [/system ntp server get enabled]] . " fw-regel=" . [:tostr [:len [/ip firewall filter find comment="TP5-E4: ntp"]]])

View File

@@ -0,0 +1,18 @@
# NTP, zweiter Teil: der Core braucht selbst eine Zeitquelle.
#
# BEFUND (2026-08-18, nach dem Einschalten des Servers): die Anfragen der APs
# kommen an (Firewall-Regel zaehlt), der Core antwortet aber nicht brauchbar —
# `/system ntp client` ist **enabled: no**, ohne Server, `status: stopped`, und
# `/system ntp server` steht auf `use-local-clock: no`. Ein NTP-Server, der
# selbst nicht synchronisiert ist, liefert Stratum 16; Clients verwerfen das
# und bleiben genau da stehen, wo ap1/ap2 stehen: `waiting`.
#
# Damit ist die Kette vollstaendig: der Standort hatte **an keiner Stelle** eine
# funktionierende Zeitsynchronisation. Dass die Uhren von core/branch/ap1
# trotzdem stimmten, war Restglueck vom letzten manuellen Stellen — ap2 ist
# einfach als erstes weggelaufen (17 h).
#
# Upstream ueber Namen ist unproblematisch: der Core loest seit TP23 Stufe 1
# selbst per DoH auf.
:do { /system ntp client set enabled=yes servers=time.cloudflare.com,at.pool.ntp.org } on-error={ :log info "tp19ntp: client warf" }
:log info ("tp19ntp: core-client status=" . [/system ntp client get status])

View File

@@ -0,0 +1,14 @@
# NTP-Server per DHCP ausliefern (Christophs Vorschlag 2026-08-18).
#
# WARUM DAS BESSER IST ALS PRO GERAET EINZUTRAGEN: bisher kannte nur die
# MikroTik-Flotte eine Zeitquelle, und die per Hand. Ueber `ntp-server=` im
# dhcp-server network bekommt **jeder** DHCP-Client den Standort-NTP-Server
# mitgeliefert (DHCP-Option 42) — ohne dass irgendwo eine IP gepflegt werden
# muss. Das Gateway des jeweiligen VLAN ist immer .1, also traegt jedes Netz
# seinen eigenen ein.
#
# Voraussetzung ist erfuellt: der Core ist seit heute selbst `synchronized`
# (tp19-ntp-core-client.rsc) und laeuft als Server (tp19-ntp-core-als-zeitquelle.rsc,
# inkl. der input-Regel, ohne die er seit TP5 Etappe 4 unerreichbar waere).
:foreach n in=[/ip dhcp-server network find] do={ :do { /ip dhcp-server network set $n ntp-server=([/ip dhcp-server network get $n gateway]) } on-error={ :log info "tp19ntp: dhcp-option warf" } }
:log info ("tp19ntp: dhcp-netze-mit-ntp=" . [:tostr [:len [/ip dhcp-server network find where ntp-server!=""]]] . " von " . [:tostr [:len [/ip dhcp-server network find]]])

View File

@@ -0,0 +1,71 @@
# TP21.1 — crs326-branch Bootstrap (Stand 2026-08-08, Rev 4)
#
# Hintergrund:
# - WinBox-MAC-Scan zeigt 0.0.0.0, solange das Gerät auf dem Discovery-VLAN keine IP hat.
# - Sichere Bring-up-Reihenfolge: Ports → VLAN-Tabelle → VLAN-Interfaces → IPs →
# vlan-filtering → Werkseinstellungen bereinigen.
# - bridge MUSS in jeder Bridge-VLAN-Zeile tagged sein, sonst sieht die CPU das VLAN nicht.
# - NAS-Ports (ether2/8) sind tagged VLAN20 (NAS spricht selbst tagged).
#
# Vor dem Import: ether1 (Uplink) und ether24 (ap2) abziehen.
# Nach dem Import: WinBox auf 10.40.10.2 verbinden, dann uplink einstecken.
/system identity set name=crs326-branch
/interface bridge port
# Trunk-Ports (Uplink + AP + NAS)
set [find interface=ether1] pvid=1 disabled=no
set [find interface=ether2] pvid=1 disabled=no
set [find interface=ether8] pvid=1 disabled=no
set [find interface=ether24] pvid=1 disabled=no
# ACCESS-Ports
set [find interface=ether3] pvid=10 disabled=no
set [find interface=ether4] pvid=10 disabled=no
set [find interface=ether5] pvid=40 disabled=no
set [find interface=ether6] pvid=40 disabled=no
set [find interface=ether7] pvid=20 disabled=no
# Restliche Ethernet-Ports flexibel als INFRA vorbelegen, aber enabled
set [find interface=ether9] pvid=20 disabled=no
set [find interface=ether10] pvid=20 disabled=no
set [find interface=ether11] pvid=20 disabled=no
set [find interface=ether12] pvid=20 disabled=no
set [find interface=ether13] pvid=20 disabled=no
set [find interface=ether14] pvid=20 disabled=no
set [find interface=ether15] pvid=20 disabled=no
set [find interface=ether16] pvid=20 disabled=no
set [find interface=ether17] pvid=20 disabled=no
set [find interface=ether18] pvid=20 disabled=no
set [find interface=ether19] pvid=20 disabled=no
set [find interface=ether20] pvid=20 disabled=no
set [find interface=ether21] pvid=20 disabled=no
set [find interface=ether22] pvid=20 disabled=no
set [find interface=ether23] pvid=20 disabled=no
set [find interface=sfp-sfpplus1] pvid=1 disabled=yes
set [find interface=sfp-sfpplus2] pvid=1 disabled=yes
/interface bridge vlan
add bridge=bridge tagged=ether1,ether24,bridge untagged=ether3,ether4 vlan-ids=10 comment=MAIN
add bridge=bridge tagged=ether1,ether2,ether8,ether24,bridge untagged=ether7,ether9,ether10,ether11,ether12,ether13,ether14,ether15,ether16,ether17,ether18,ether19,ether20,ether21,ether22,ether23 vlan-ids=20 comment=INFRA
add bridge=bridge tagged=ether1,ether24,bridge vlan-ids=30 comment=IOT
add bridge=bridge tagged=ether1,ether24,bridge untagged=ether5,ether6 vlan-ids=40 comment=MEDIA
add bridge=bridge tagged=ether1,ether24,bridge vlan-ids=50 comment=UNTRUSTED
add bridge=bridge tagged=ether1,ether24,bridge vlan-ids=90 comment=GUEST
add bridge=bridge tagged=ether1,ether24,bridge vlan-ids=99 comment=MGMT
/interface vlan add name=vlan10 interface=bridge vlan-id=10 comment="TEMP Mgmt VLAN10"
/interface vlan add name=vlan99 interface=bridge vlan-id=99 comment=MGMT
/ip address
add address=10.40.99.2/24 interface=vlan99 comment="TP21-mgmt-target"
add address=10.40.10.2/24 interface=vlan10 comment="TEMP Bring-up VLAN10"
/ip dns set servers=10.40.99.1 allow-remote-requests=no
/interface bridge set [find name=bridge] vlan-filtering=yes
/ip route add dst-address=0.0.0.0/0 gateway=10.40.99.1 comment="TP21-default"
:do { /ip address remove [find address="192.168.88.1/24"] } on-error={}
:do { /ip dhcp-client disable [find interface=bridge] } on-error={}

View File

@@ -0,0 +1,82 @@
# TP21.1 core DHCP restore from ap1-pre-core-cutover-20260806-073616.rsc
## options
:do { /ip dhcp-server option add code=6 name="dns 1.1.1.1" value=0x01010101 } on-error={}
## pools
:do { /ip pool add name=pool-v10 ranges=10.40.10.100-10.40.10.200 } on-error={}
:do { /ip pool add name=pool-v20 ranges=10.40.20.100-10.40.20.200 } on-error={}
:do { /ip pool add name=pool-v30 ranges=10.40.30.100-10.40.30.250 } on-error={}
:do { /ip pool add name=pool-v40 ranges=10.40.40.100-10.40.40.200 } on-error={}
:do { /ip pool add name=pool-v50 ranges=10.40.50.100-10.40.50.200 } on-error={}
:do { /ip pool add name=pool-v90 ranges=10.40.90.100-10.40.90.200 } on-error={}
:do { /ip pool add name=pool-v99 ranges=10.40.99.100-10.40.99.200 } on-error={}
## dhcp-servers
:do { /ip dhcp-server add address-pool=pool-v10 comment=MAIN interface=vlan10 lease-time=1d name=dhcp-v10 } on-error={}
:do { /ip dhcp-server add address-pool=pool-v20 comment=INFRA interface=vlan20 lease-time=1d name=dhcp-v20 } on-error={}
:do { /ip dhcp-server add address-pool=pool-v30 comment=IOT interface=vlan30 lease-time=1d name=dhcp-v30 } on-error={}
:do { /ip dhcp-server add address-pool=pool-v40 comment=MEDIA interface=vlan40 lease-time=1d name=dhcp-v40 } on-error={}
:do { /ip dhcp-server add address-pool=pool-v50 comment=UNTRUSTED interface=vlan50 lease-time=1d name=dhcp-v50 } on-error={}
:do { /ip dhcp-server add address-pool=pool-v90 comment=GUEST interface=vlan90 lease-time=12h name=dhcp-v90 } on-error={}
:do { /ip dhcp-server add address-pool=pool-v99 comment=MGMT interface=vlan99 lease-time=1d name=dhcp-v99 } on-error={}
## networks
:do { /ip dhcp-server network add address=10.40.10.0/24 comment=MAIN dns-server=10.40.10.1 gateway=10.40.10.1 } on-error={}
:do { /ip dhcp-server network add address=10.40.20.0/24 comment=INFRA dns-server=10.40.20.1 gateway=10.40.20.1 } on-error={}
:do { /ip dhcp-server network add address=10.40.30.0/24 comment=IOT dns-server=10.40.30.1 gateway=10.40.30.1 } on-error={}
:do { /ip dhcp-server network add address=10.40.40.0/24 comment=MEDIA dns-server=10.40.40.1 gateway=10.40.40.1 } on-error={}
:do { /ip dhcp-server network add address=10.40.50.0/24 comment=UNTRUSTED dns-server=10.40.50.1 gateway=10.40.50.1 } on-error={}
:do { /ip dhcp-server network add address=10.40.90.0/24 comment=GUEST dns-server=10.40.90.1 gateway=10.40.90.1 } on-error={}
:do { /ip dhcp-server network add address=10.40.99.0/24 comment=MGMT dns-server=10.40.99.1 gateway=10.40.99.1 } on-error={}
## leases
:do { /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 } on-error={}
:do { /ip dhcp-server lease 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 } on-error={}
:do { /ip dhcp-server lease 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 } on-error={}
:do { /ip dhcp-server lease 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 } on-error={}
:do { /ip dhcp-server lease 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 } on-error={}
:do { /ip dhcp-server lease 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 } on-error={}
:do { /ip dhcp-server lease 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 } on-error={}
:do { /ip dhcp-server lease 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 } on-error={}
:do { /ip dhcp-server lease 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 } on-error={}
:do { /ip dhcp-server lease 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 } on-error={}
:do { /ip dhcp-server lease 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 } on-error={}
:do { /ip dhcp-server lease 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 } on-error={}
:do { /ip dhcp-server lease add address=10.40.40.198 comment=SonosTerrasse mac-address=78:28:CA:D2:0D:AC server=dhcp-v40 } on-error={}
:do { /ip dhcp-server lease add address=10.40.30.202 comment=shelly-s22_kueche_steckdose3-2 mac-address=A0:85:E3:C8:65:34 server=dhcp-v30 } on-error={}
:do { /ip dhcp-server lease add address=10.40.30.203 comment=shelly-s23_dieleug-wohnzimmer_licht mac-address=AC:15:18:7B:62:20 server=dhcp-v30 } on-error={}
:do { /ip dhcp-server lease add address=10.40.30.205 comment=shelly-s25_bad_lichtschalter mac-address=98:A3:16:79:38:50 server=dhcp-v30 } on-error={}
:do { /ip dhcp-server lease add address=10.40.30.206 comment=shelly-s20_kueche_steckdosen2 mac-address=98:A3:16:7C:5F:EC server=dhcp-v30 } on-error={}
:do { /ip dhcp-server lease add address=10.40.30.207 comment=shelly-s21_kueche_steckdosen4 mac-address=20:6E:F1:01:F9:F8 server=dhcp-v30 } on-error={}
:do { /ip dhcp-server lease add address=10.40.30.208 comment=shelly-s1_weihnachten_kueche mac-address=E4:B0:63:D7:C7:A4 server=dhcp-v30 } on-error={}
:do { /ip dhcp-server lease add address=10.40.30.209 comment=shelly-s2_weihnachten_terrasse mac-address=28:37:2F:36:F4:7C server=dhcp-v30 } on-error={}
:do { /ip dhcp-server lease add address=10.40.30.210 comment=shelly-s3_weihnachten_kinderzimmer mac-address=E4:B0:63:E4:EF:7C server=dhcp-v30 } on-error={}
:do { /ip dhcp-server lease add address=10.40.30.211 comment=shelly-s4_kueche_mikrowelle mac-address=E4:B0:63:E4:0F:30 server=dhcp-v30 } on-error={}
:do { /ip dhcp-server lease add address=10.40.30.212 comment=shelly-s5_kueche_backofen mac-address=D8:85:AC:0C:5C:C8 server=dhcp-v30 } on-error={}
:do { /ip dhcp-server lease add address=10.40.30.213 comment=shelly-s6_kueche_kuehlschrank mac-address=D0:CF:13:D8:18:FC server=dhcp-v30 } on-error={}
:do { /ip dhcp-server lease add address=10.40.30.214 comment=shelly-s7_kueche_tiefkuehlschrank mac-address=D0:CF:13:D9:94:E8 server=dhcp-v30 } on-error={}
:do { /ip dhcp-server lease add address=10.40.30.215 comment=shelly-s8_kueche_geschirrspueler mac-address=E4:B0:63:FA:FD:E0 server=dhcp-v30 } on-error={}
:do { /ip dhcp-server lease add address=10.40.30.216 comment=shelly-s9_wohnzimmer_sideboard mac-address=E8:F6:0A:61:88:08 server=dhcp-v30 } on-error={}
:do { /ip dhcp-server lease add address=10.40.30.217 comment=shelly-s10_bad_sonos mac-address=E4:B0:63:FC:57:E4 server=dhcp-v30 } on-error={}
:do { /ip dhcp-server lease add address=10.40.30.218 comment=shelly-s11_kinderzimmer_sonos mac-address=E4:B0:63:FA:6C:00 server=dhcp-v30 } on-error={}
:do { /ip dhcp-server lease add address=10.40.30.219 comment=shelly-s12_dieleug_steckdosen mac-address=48:F6:EE:C8:E6:AC server=dhcp-v30 } on-error={}
:do { /ip dhcp-server lease add address=10.40.30.220 comment=shelly-s13_arbeitszimmer_technik mac-address=48:F6:EE:DD:6A:F4 server=dhcp-v30 } on-error={}
:do { /ip dhcp-server lease add address=10.40.30.221 comment=shelly-s14_schlafzimmer_bett mac-address=E4:B0:63:FC:59:48 server=dhcp-v30 } on-error={}
:do { /ip dhcp-server lease add address=10.40.30.222 comment=shelly-s15_technik_pve mac-address=E4:B0:63:FA:D5:34 server=dhcp-v30 } on-error={}
:do { /ip dhcp-server lease add address=10.40.30.223 comment=shelly-s16_wohnzimmer_steckdosen mac-address=10:20:BA:FA:F5:40 server=dhcp-v30 } on-error={}
:do { /ip dhcp-server lease add address=10.40.30.224 comment=shelly-s17_bad_steckdosen mac-address=10:20:BA:FB:6D:64 server=dhcp-v30 } on-error={}
:do { /ip dhcp-server lease add address=10.40.30.225 comment=shelly-s18_kueche_steckdosen1 mac-address=48:F6:EE:CC:16:E4 server=dhcp-v30 } on-error={}
:do { /ip dhcp-server lease add address=10.40.30.226 comment=shelly-s19_kueche_steckdosen3 mac-address=10:20:BA:FB:70:FC server=dhcp-v30 } on-error={}
:do { /ip dhcp-server lease add address=10.40.30.227 comment=switchbot-floorlamp mac-address=B8:F8:62:09:F2:04 server=dhcp-v30 } on-error={}
:do { /ip dhcp-server lease add address=10.40.30.228 comment=Twinkly_Weihnachtsgirlande mac-address=98:F4:AB:2C:A7:88 server=dhcp-v30 } on-error={}
:do { /ip dhcp-server lease add address=10.40.30.230 comment=Withings_waage mac-address=00:24:E4:40:2A:72 server=dhcp-v30 } on-error={}
:do { /ip dhcp-server lease add address=10.40.30.231 comment=Withings_blutdruck mac-address=A4:7E:FA:18:2C:64 server=dhcp-v30 } on-error={}
:do { /ip dhcp-server lease add address=10.40.30.232 comment=pve-homematic-raspi-ccu mac-address=BC:24:11:8E:0A:57 server=dhcp-v30 } on-error={}
:do { /ip dhcp-server lease add address=10.40.30.233 comment=shelly-s26_bad_luefter mac-address=98:A3:16:7A:A3:54 server=dhcp-v30 } on-error={}
:do { /ip dhcp-server lease add address=10.40.30.236 comment=lichterkette mac-address=A4:F0:0F:5F:D0:EC server=dhcp-v30 } on-error={}
:do { /ip dhcp-server lease add address=10.40.30.237 comment=lichterkette-mini mac-address=24:DC:C3:9B:89:64 server=dhcp-v30 } on-error={}
:do { /ip dhcp-server lease add address=10.40.30.241 comment=NukiSmartlock mac-address=28:DB:A7:09:7B:54 server=dhcp-v30 } on-error={}
:do { /ip dhcp-server lease add address=10.40.30.244 comment=NukiBridge mac-address=24:62:AB:B1:63:F4 server=dhcp-v30 } on-error={}
:do { /ip dhcp-server lease add address=10.40.30.8 comment=DTUBI-414392337823 mac-address=54:F2:9F:12:51:F0 server=dhcp-v30 } on-error={}
:do { /ip dhcp-server lease add address=10.40.20.10 comment=nas-bf mac-address=90:09:D0:58:20:F9 server=dhcp-v20 } on-error={}
:do { /ip dhcp-server lease add address=10.40.40.11 comment=tv mac-address=70:AF:24:B8:C9:86 server=dhcp-v40 } on-error={}
:do { /ip dhcp-server lease add address=10.40.30.5 comment=P750W mac-address=4C:EB:BD:E3:25:F2 server=dhcp-v30 } on-error={}
:do { /ip dhcp-server lease 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 } on-error={}
:do { /ip dhcp-server lease add address=10.40.30.204 comment=shelly-s24_bad_spiegelschrank mac-address=14:33:5C:D8:2D:7C server=dhcp-v30 } on-error={}
:do { /ip dhcp-server lease 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 } on-error={}

View File

@@ -0,0 +1,40 @@
# TP21.1 — crs326-core WAN + WG-Hub + NAT restore (2026-08-08)
#
# Kontext: Beim Core-Cutover (ap1 -> crs326-core als L3-Router) fehlten nach dem
# ap1-Plattmachen auf core noch WAN-Egress und der WG-Hub. DHCP wurde separat
# via tp21-core-dhcp-from-ap1.rsc wiederhergestellt.
# Quelle = letztes ap1-Backup routeros/ap1-pre-core-cutover-20260806-073616.rsc.
#
# WICHTIG (disabled-Falle): ap1 hatte 3 Masquerade-Regeln, davon 2 disabled=yes
# (out-interface=bridge / out-interface=ether1). Auf core NUR die WAN-Variante
# (out-interface-list=WAN, wobei WAN-Liste = vlan100). Die disabled-Regeln NICHT
# übernehmen.
#
# Der WG-Privkey ist ein Secret und steht NICHT hier -> aus routeros/ap1-wg-hub.privkey
# (gitignored) einsetzen. Der abgeleitete Hub-Pubkey bleibt identisch
# (P/bztIk13cAmLHbN6oHGGnWAKLZsqR5TXAbFP+NGGBk=), damit FM/DW-Spokes weiter matchen.
# --- WG-Hub (TP10 Multi-Site VPN) ---
:do { /interface wireguard add name=wg-hub listen-port=51821 mtu=1412 comment="TP10 Multi-Site VPN Hub (FM/DW spokes)" } on-error={}
:do { /interface wireguard set [find name=wg-hub] private-key="<aus routeros/ap1-wg-hub.privkey>" } on-error={}
:do { /interface wireguard peers add interface=wg-hub name=fm comment=fm allowed-address=10.101.0.0/24 endpoint-address=abxhutfgsid9snmy.myfritz.net endpoint-port=52476 public-key="dzRY0lnnUygijChWe5wXZmXPeNcbdALpqBptA3fyiVE=" } on-error={}
:do { /interface wireguard peers add interface=wg-hub name=dw comment=dw allowed-address=10.95.10.0/24 public-key="FqYZLztQ/qME5VDie5QLGH++5299WJiE1dPpQnPbzUw=" } on-error={}
:do { /ip route add dst-address=10.101.0.0/24 gateway=wg-hub comment="TP10: FM spoke -> wg-hub" } on-error={}
:do { /ip route add dst-address=10.95.10.0/24 gateway=wg-hub comment="TP10: DW spoke -> wg-hub" } on-error={}
# --- NAT (nur WAN-Variante; bridge/ether1-Masquerade bewusst NICHT) ---
:do { /ip firewall nat add action=accept chain=srcnat dst-address=10.40.0.0/16 comment="TP4: no-NAT inter-VLAN (LAN-internal, before masquerade)" } on-error={}
:do { /ip firewall nat add action=masquerade chain=srcnat out-interface-list=WAN ipsec-policy=out,none comment="defconf: masquerade" } on-error={}
# --- WAN dhcp-client auf vlan100 (Liwest); Blackhole-Selbst-Default-Route weg ---
:do { /ip dhcp-client add interface=vlan100 use-peer-dns=no use-peer-ntp=no comment="WAN Arris DHCP (Liwest) via vlan100" } on-error={}
:do { /ip route remove [find dst-address=0.0.0.0/0 gateway=10.40.99.1] } on-error={}
# --- DNS-Resolver (PROVISORISCH plain, DoH-TODO s.u.) ---
# core hatte allow-remote-requests=no + leeren Upstream -> Client-DNS tot + FM-Hub
# konnte myfritz nicht auflösen. Fix (plain, weil core keine CA-Certs hat -> certs=0,
# verify-doh-cert unmöglich):
:do { /ip dns set allow-remote-requests=yes servers=1.1.1.1 } on-error={}
# TODO (spätere Session, VOR/mit WLAN-Rebuild): CA-Bundle auf core importieren, dann
# DoH-Cloudflare-Gateway wie ap1 setzen:
# /ip dns set use-doh-server=https://px35ynlol3.cloudflare-gateway.com/dns-query verify-doh-cert=yes

View File

@@ -0,0 +1,48 @@
# TP23 Stufe 1 — DoH auf crs326-core (schliesst zugleich TP21.1 TODO 1).
#
# WARUM NUR EIN ROOT UND KEIN CA-BUNDLE
# ------------------------------------
# Die Kette von px35ynlol3.cloudflare-gateway.com ist
# Leaf(CN=cloudflare-gateway.com) -> GTS WE1 -> GTS Root R4
# (am 2026-08-17 per openssl s_client verifiziert). Fuer verify-doh-cert=yes
# reicht der Root. Das ist hier kein Geiz, sondern Notwendigkeit: der Core hat
# nur ~2,8 MB freien Flash, ein Mozilla-Bundle (~230 KB) waere Verschwendung
# und muesste gepflegt werden. Der Root laeuft bis 2036, das Leaf rotiert
# darunter ohne Zutun.
#
# Datei liegt schon auf dem Geraet: gtsrootr4.pem (per SFTP hochgeladen statt
# per fetch geholt — fetch haette check-certificate=no gebraucht, also genau
# die Pruefung ausgehebelt, die wir gerade aufbauen wollen).
# SHA-256 des Roots, lokal gegengeprueft:
# 34:9D:FA:40:58:C5:E2:63:12:3B:39:8A:E7:95:57:3C:4E:13:13:C8:3F:E6:8F:93:55:6C:D5:E8:03:1B:3C:7D
#
# servers=1.1.1.1 BLEIBT stehen: RouterOS loest damit den DoH-Hostnamen selbst
# auf (Bootstrap). Ohne das haette man ein Henne-Ei. Alles andere laeuft danach
# ueber DoH.
#
# Push: python3 nautobot/gc-manual-plan.py --device crs326-core \
# --commands-file routeros/tp23-stufe1-doh-core.rsc \
# --change-id TP23-Stufe1-DoH --deploy \
# --assert '/ip/dns|use-doh-server=https://px35ynlol3.cloudflare-gateway.com/dns-query' \
# --assert '/ip/dns|verify-doh-cert=true'
# Backup davor: core-pre-tp23-doh-20260817.backup
#
# Rollback: /ip dns set use-doh-server="" — eine Zeile, sofort wirksam.
# 1) Import. Idempotent: nur wenn der Root noch nicht da ist.
:do { :if ([:len [/certificate find where common-name="GTS Root R4"]] = 0) do={ /certificate import file-name=gtsrootr4.pem passphrase="" } } on-error={ :log info "tp23s1: 01 import warf" }
# 2) Explizit vertrauen. Der Import allein setzt trusted nicht zuverlaessig,
# und ohne trusted=yes scheitert verify-doh-cert=yes stumm.
:do { /certificate set [find where common-name="GTS Root R4"] trusted=yes } on-error={ :log info "tp23s1: 02 trust warf" }
# 3) DoH scharf. verify-doh-cert=yes ist der ganze Punkt der Uebung — ohne
# Pruefung waere DoH nur Verschleierung, kein Vertrauensanker.
:do { /ip dns set use-doh-server=https://px35ynlol3.cloudflare-gateway.com/dns-query verify-doh-cert=yes } on-error={ :log info "tp23s1: 03 doh-set warf" }
# 4) Cache leeren, damit die naechste Antwort nachweislich ueber DoH kommt und
# nicht aus dem Bestand von vorher.
:do { /ip dns cache flush } on-error={ :log info "tp23s1: 04 flush warf" }
# Selbstprotokoll — der Deploy-Job zeigt den Geraete-Output nicht.
:log info ("tp23s1: ENDE roots=" . [:tostr [:len [/certificate find where common-name="GTS Root R4"]]] . " trusted=" . [:tostr [/certificate get [find where common-name="GTS Root R4"] trusted]] . " doh=" . [/ip dns get use-doh-server])

View File

@@ -0,0 +1,31 @@
# TP25 — dirtyvader-infra offline nehmen (User-Entscheid 2026-08-17).
#
# Begruendung: INFRA-Geraete haengen am Kabel (schon in TP3 als "vertagt, Mgmt
# verkabelt" angelegt). Die SSID hatte auf BEIDEN APs und BEIDEN Baendern
# 0 Clients. Zwei VAPs weniger je AP = weniger Beacon-Airtime.
#
# 🔑 WARUM disable UND NICHT "hidden" (Christophs Rueckfrage):
# Eine versteckte SSID sendet ihr Beacon WEITER — sie laesst nur das
# SSID-Feld darin leer. Verstecken spart also KEINE Airtime, es macht die
# SSID nur unbequemer zu finden (und ist kein Sicherheitsgewinn).
# Airtime spart ausschliesslich das Abschalten der VAP.
#
# Reversibel: disabled=no setzt sie zurueck. sec-infra/dp-infra/cfg-infra
# bleiben bestehen — die Zone ist damit jederzeit ohne Neuaufbau reaktivierbar.
#
# ⚠️ NICHT compliance-ueberwacht: die Sektion /interface wifi hat (Stand
# 2026-08-17) keine Compliance-Rule. Diese Aenderung ist deshalb weder
# Drift noch drift-gesichert — sie faellt unter die offene Luecke aus
# TP25 Schritt 6.
#
# Push (BEIDE APs):
# python3 nautobot/gc-manual-plan.py --device ap2 \
# --commands-file routeros/tp25-infra-vaps-offline.rsc \
# --change-id TP25-infra-offline-ap2 --deploy
# ... dasselbe mit --device ap1
:do { /interface/wifi/set [find name=w2-infra] disabled=yes } on-error={ :log info "tp25i: w2-infra warf" }
:do { /interface/wifi/set [find name=w5-infra] disabled=yes } on-error={ :log info "tp25i: w5-infra warf" }
# Gegenprobe im Log: erwartet aktive-vaps=9 (vorher 11), infra-disabled=true/true
:log info ("tp25i: aktive-vaps=" . [:tostr [:len [/interface/wifi/find where !disabled]]] . " w2infra=" . [:tostr [/interface/wifi/get [find name=w2-infra] disabled]] . " w5infra=" . [:tostr [/interface/wifi/get [find name=w5-infra] disabled]])

View File

@@ -0,0 +1,35 @@
# TP25 Schritt 1-3 auf ap1 — identisch zu ap2, mit der auf ap2 verifizierten Syntax.
#
# Reihenfolge bewusst: ap2 war zuerst (weniger kritisch), dort ist die Syntax
# am Geraet belegt. ap1 folgt erst jetzt.
#
# ⚠️ ap1 MUSS mit, sonst bleibt die Ueberlappung: ap1 auf 5180 mit 80 MHz
# belegt 5170-5250 und damit auch ap2s neue 40-MHz-Zelle bei 5220.
# Ziel: ap1 = 5180/Ce (ch36+40, 5170-5210), ap2 = 5220/Ce (ch44+48,
# 5210-5250). Benachbart, ueberlappungsfrei, beide 18 dBm.
#
# ap1-Besonderheit: w2-iot traegt hier ZUSAETZLICH channel.band=2ghz-n gegen
# einen 2ghz-ax-Master — der eigentliche Fund D. Deshalb !channel.band mit dabei.
#
# Push: python3 nautobot/gc-manual-plan.py --device ap1 \
# --commands-file routeros/tp25-schritt1-3-ap1.rsc \
# --change-id TP25-Funkparameter-ap1 --deploy
# Backup davor: ap1-pre-tp25-20260817.backup
# --- Schritt 3 zuerst: Profil auf Sollwert, damit das Leeren unten direkt greift
:do { /interface/wifi/channel/set [find name=ch-5g] frequency=5180 width=20/40mhz } on-error={ :log info "tp25a1: 01 ch-5g warf" }
:do { /interface/wifi/channel/set [find name=ch-2g] frequency=2412 width=20mhz } on-error={ :log info "tp25a1: 02 ch-2g warf" }
# --- Schritt 2: Inline-Overrides einzeln leeren (!channel allein tut NICHTS) -
:do { /interface/wifi/set [find default-name=wifi1] !channel.width } on-error={ :log info "tp25a1: 03 wifi1 !width warf" }
:do { /interface/wifi/set [find default-name=wifi1] !channel.band } on-error={ :log info "tp25a1: 04 wifi1 !band warf" }
:do { /interface/wifi/set [find default-name=wifi1] !channel.skip-dfs-channels } on-error={ :log info "tp25a1: 05 wifi1 !skipdfs warf" }
:do { /interface/wifi/set [find default-name=wifi2] !channel.width } on-error={ :log info "tp25a1: 06 wifi2 !width warf" }
:do { /interface/wifi/set [find default-name=wifi2] !channel.band } on-error={ :log info "tp25a1: 07 wifi2 !band warf" }
:do { /interface/wifi/set [find default-name=wifi2] !channel.skip-dfs-channels } on-error={ :log info "tp25a1: 08 wifi2 !skipdfs warf" }
# --- Schritt 1: Fund D — Slave-VAP-Override weg -----------------------------
:do { /interface/wifi/set [find name=w2-iot] !channel.width } on-error={ :log info "tp25a1: 09 w2-iot !width warf" }
:do { /interface/wifi/set [find name=w2-iot] !channel.band } on-error={ :log info "tp25a1: 10 w2-iot !band warf" }
:log info ("tp25a1: wifi1-width=" . [:tostr [/interface/wifi/get [find default-name=wifi1] channel.width]] . " wifi2-width=" . [:tostr [/interface/wifi/get [find default-name=wifi2] channel.width]] . " w2iot-band=" . [:tostr [/interface/wifi/get [find name=w2-iot] channel.band]])

View File

@@ -0,0 +1,43 @@
# TP25 Schritt 1-3 auf ap2 — Funkparameter nach Manual statt Bauchgefuehl.
#
# Schritt 1: unzulaessigen channel.*-Override von w2-iot entfernen (Slave-VAP
# kann keinen eigenen Kanal haben; auf ap1 erzwang er sogar 2ghz-n
# gegen einen 2ghz-ax-Master).
# Schritt 2: Inline-channel.*-Overrides auf wifi1/wifi2 leeren, damit die
# verlinkten Profile ch-5g/ch-2g ueberhaupt greifen. Das Override
# gewinnt sonst — deshalb lief 2,4 GHz auf 40 MHz, obwohl ch-2g
# width=20mhz sagt.
# Schritt 3: ch-5g auf 20/40mhz + Frequenz 5220 (Indoor-Band, statt 5745
# ETSI-Band-C mit gedrosselter Leistung -> Messung s.u.).
#
# MESSZWECK: nach dem Deploy tx-power auf wifi1 lesen. Erwartung 18 dBm statt 9.
# Bestaetigt oder widerlegt die Diagnose "ch149 kostet 9 dB" AM GERAET.
#
# Push: python3 nautobot/gc-manual-plan.py --device ap2 \
# --commands-file routeros/tp25-schritt1-3-ap2.rsc \
# --change-id TP25-Funkparameter-ap2 --deploy
# Backup davor: ap2-pre-tp25-20260817.backup
#
# ⚠️ Wirft alle Clients kurz raus (Channel-/VAP-Re-Apply, bei TP3.3 gemessen).
# --- Schritt 1: w2-iot Slave-Override weg -----------------------------------
:do { /interface/wifi/set [find name=w2-iot] !channel } on-error={ :log info "tp25: 01 w2-iot !channel warf" }
# --- Schritt 2: Inline-Overrides auf den Master-Radios weg ------------------
# Nur channel.* wird geleert. configuration=/configuration.manager/.mode
# bleiben unangetastet — die sind korrekt und tragen die SSID-Bindung.
:do { /interface/wifi/set [find default-name=wifi1] !channel } on-error={ :log info "tp25: 02 wifi1 !channel warf" }
:do { /interface/wifi/set [find default-name=wifi2] !channel } on-error={ :log info "tp25: 03 wifi2 !channel warf" }
# --- Schritt 3: Profile auf Manual-konforme Werte ---------------------------
# 5 GHz: 80 -> 40 MHz. Kostet Spitzendurchsatz, den hier niemand braucht, und
# gewinnt ~3 dB Randabdeckung. Erst 40 MHz macht zwei ueberlappungsfreie
# Zellen im Indoor-Band ueberhaupt moeglich (dort liegt nur EIN 80-MHz-Block).
:do { /interface/wifi/channel/set [find name=ch-5g] frequency=5220 width=20/40mhz } on-error={ :log info "tp25: 04 ch-5g warf" }
# 2,4 GHz: Profil steht schon auf 20mhz — nach Schritt 2 greift es endlich.
# Zur Sicherheit explizit, damit der Sollzustand in der SoT eindeutig ist.
:do { /interface/wifi/channel/set [find name=ch-2g] frequency=2462 width=20mhz } on-error={ :log info "tp25: 05 ch-2g warf" }
# --- Selbstprotokoll (Deploy-Job zeigt den Geraete-Output nicht) ------------
:log info ("tp25ap2: ch-5g=" . [/interface/wifi/channel/get [find name=ch-5g] frequency] . "/" . [/interface/wifi/channel/get [find name=ch-5g] width] . " ch-2g=" . [/interface/wifi/channel/get [find name=ch-2g] frequency] . "/" . [/interface/wifi/channel/get [find name=ch-2g] width])

View File

@@ -0,0 +1,42 @@
# TP25 Schritt 2 — KORREKTUR. Die Inline-Overrides sind noch da.
#
# 🔴 GOTCHA (2026-08-17 am Geraet belegt): `/interface/wifi/set <if> !channel`
# loescht die Inline-Sub-Property-Overrides NICHT. Es wirft auch nicht — es
# tut einfach nichts Sichtbares. Nach dem Lauf stand auf ap2 wifi1 weiterhin
# `channel.width=20/40/80mhz`, obwohl das Profil ch-5g auf 20/40mhz steht
# und cfg-main-5g korrekt zu 20/40mhz aufloest.
# => Sub-Properties muessen EINZELN geleert werden: !channel.width usw.
# Gleiches Muster wie der bekannte `!configuration.ssid !security.*`-Griff
# aus dem TP3-Bring-up (CLAUDE.md) — dort nur nie auf channel.* angewandt.
#
# 🔴 DRINGLICHKEIT: weil die Breite noch auf 80 MHz stand, ist ap2 mit dem
# Frequenzwechsel auf 5220/eeCe in DENSELBEN 80-MHz-Block wie ap1
# (5180/Ceee) gerutscht — beide belegen ch36-48. Das ist die Co-Channel-Lage,
# die TP3 2026-06-18 ueberhaupt erst wegkonfigurieren wollte. Diese Datei
# stellt den ueberlappungsfreien Zustand her.
#
# Push: python3 nautobot/gc-manual-plan.py --device ap2 \
# --commands-file routeros/tp25-schritt2-fix-overrides-ap2.rsc \
# --change-id TP25-Overrides-ap2 --deploy
# Backup: ap2-pre-tp25-20260817.backup gilt weiter.
#
# SOLL danach: wifi1 = 5220/ax/Ce/I (40 MHz, ch44+48), wifi2 = 2462/ax (20 MHz).
# --- wifi1: jede Sub-Property einzeln leeren --------------------------------
:do { /interface/wifi/set [find default-name=wifi1] !channel.width } on-error={ :log info "tp25f: 01 wifi1 !width warf" }
:do { /interface/wifi/set [find default-name=wifi1] !channel.band } on-error={ :log info "tp25f: 02 wifi1 !band warf" }
:do { /interface/wifi/set [find default-name=wifi1] !channel.skip-dfs-channels } on-error={ :log info "tp25f: 03 wifi1 !skipdfs warf" }
# --- wifi2: dito. Danach greift ch-2g mit width=20mhz --------------------
:do { /interface/wifi/set [find default-name=wifi2] !channel.width } on-error={ :log info "tp25f: 04 wifi2 !width warf" }
:do { /interface/wifi/set [find default-name=wifi2] !channel.band } on-error={ :log info "tp25f: 05 wifi2 !band warf" }
:do { /interface/wifi/set [find default-name=wifi2] !channel.skip-dfs-channels } on-error={ :log info "tp25f: 06 wifi2 !skipdfs warf" }
# --- w2-iot: der eigentliche Fund D ----------------------------------------
# Ein Slave-VAP darf keinen eigenen Kanal haben. Auf ap2 ist es nur width,
# auf ap1 zusaetzlich band=2ghz-n gegen einen 2ghz-ax-Master.
:do { /interface/wifi/set [find name=w2-iot] !channel.width } on-error={ :log info "tp25f: 07 w2-iot !width warf" }
:do { /interface/wifi/set [find name=w2-iot] !channel.band } on-error={ :log info "tp25f: 08 w2-iot !band warf" }
# --- Selbstprotokoll: die AUFGELOESTEN Interface-Werte, nicht das Profil ----
:log info ("tp25f: wifi1-width=" . [:tostr [/interface/wifi/get [find default-name=wifi1] channel.width]] . " wifi2-width=" . [:tostr [/interface/wifi/get [find default-name=wifi2] channel.width]] . " w2iot-width=" . [:tostr [/interface/wifi/get [find name=w2-iot] channel.width]])

View File

@@ -0,0 +1,48 @@
# TP26 — defconf-Firewallregeln auf den APs abraeumen (User-Entscheid 2026-08-17).
#
# WARUM: ap1 und ap2 tragen je 12 Werksregeln (input-seal, fasttrack, zwei
# ipsec-Zeilen, und sogar "accept to local loopback (for CAPsMAN)"). Ein reiner
# AP routet nicht — er hat keine forward-Kette zu schuetzen und keinen Verkehr,
# der seine input-Kette passiert ausser Mgmt. Die Regeln sind inert.
#
# WARUM NICHT "als Sollzustand ins Template schreiben": das hiesse Werksmuell als
# Absicht in die SoT zu schreiben. Dasselbe Argument wie bei telnet/ftp in
# TP16.1 — nichts hier nutzt sie, also gehoeren sie weg und nicht dokumentiert.
#
# ⚠️ Der Mgmt-Zugang haengt NICHT an diesen Regeln: er wird von /ip service
# (address=10.40.0.0/16,100.96.0.0/12, TP16.1) geschuetzt, nicht von der
# input-Kette. Die Werksregel "drop all not coming from LAN" faellt weg,
# aber /ip service bleibt die wirksame ACL. Kein Lockout-Risiko.
#
# Idempotent: laeuft die Datei zweimal, passiert beim zweiten Mal nichts.
#
# 🔴 GOTCHA, im ersten Anlauf am 2026-08-17 getroffen: `remove [find]` erfasst
# auch die DYNAMISCHE Regel "special dummy rule to show fasttrack counters".
# RouterOS verweigert deren Entfernung, das GESAMTE Kommando wirft, und damit
# wird *nichts* entfernt — der Plan meldet trotzdem "Completed".
# Ist-Stand war: 12 gesamt = 11 statisch + 1 dynamisch.
# => dynamische Regeln muessen ausgefiltert werden. Gleiche Klasse wie der
# dokumentierte `bridge/vlan/set nur auf [find ... and !dynamic]` in CLAUDE.md.
#
# 🔴 ZWEITER GOTCHA, direkt danach: `[find !dynamic]` ist hier ein SYNTAXFEHLER.
# Korrekt ist `[find where !dynamic]`. Und Syntaxfehler schlagen beim PARSEN
# zu — `:do {} on-error={}` faengt sie NICHT, der Handler laeuft nie. Symptom
# ist deshalb besonders tueckisch: kein Fehler im Log, keine Wirkung, Plan
# "Completed". Merkregel: on-error schuetzt vor Laufzeitfehlern, nicht vor
# Tippfehlern.
#
# Beide Fehlschlaege hat --assert gefangen. Ohne die Pruefung waeren sie
# unbemerkt geblieben — der Plan-Status meldete jedes Mal "Completed".
#
# Push (BEIDE APs), mit maschineller Abnahme. Die Assertion filtert bewusst auf
# dynamic=false — die Dummy-Regel ist kein Rueckstand, sondern nicht entfernbar:
# python3 nautobot/gc-manual-plan.py --device ap1 \
# --commands-file routeros/tp26-ap-firewall-cleanup.rsc \
# --change-id TP26-fw-cleanup-ap1 --deploy \
# --assert '/ip/firewall/filter?dynamic=false|count=0'
#
# Backups davor: ap{1,2}-pre-tp26-20260817.backup
:do { /ip firewall filter remove [find where !dynamic] } on-error={ :log info "tp26: remove warf" }
:log info ("tp26: statisch-verbleibend=" . [:tostr [:len [/ip firewall filter find where !dynamic]]] . " dynamisch=" . [:tostr [:len [/ip firewall filter find where dynamic]]])

View File

@@ -0,0 +1,37 @@
# TP27 — RouterBOOT-Firmware auf 7.24 nachziehen (2026-08-17).
#
# KONTEXT: Christoph hat alle vier MikroTik selbst auf RouterOS 7.24 stable
# gehoben. Verifiziert, nichts kaputt. Was dabei offen blieb, ist die zweite
# Haelfte jedes MikroTik-Upgrades: die **RouterBOOT-Firmware** haengt zurueck.
#
# crs326-core 7.19.5 -> 7.24
# crs326-branch 7.19.5 -> 7.24
# ap1 7.23.2 -> 7.24
# ap2 7.23.2 -> 7.24
#
# `/system routerboard upgrade` SCHREIBT die Firmware nur; wirksam wird sie
# erst beim naechsten Reboot. Darum: erst schreiben, dann gezielt rebooten,
# dann `current-firmware` gegenpruefen — vorher zeigt sie weiter den alten Wert.
#
# WARUM UEBER GC UND NICHT MikroMCP: `/system routerboard upgrade*` steht auf
# der MikroMCP-Deny-Liste (zu Recht — es schreibt den Bootloader). Der
# GC-Weg ist ohnehin der vorgeschriebene.
#
# Push je Geraet:
# python3 nautobot/gc-manual-plan.py --device <name> \
# --commands-file routeros/tp27-routerboot-firmware-724.rsc \
# --change-id TP27-fw-<name> --deploy
#
# Danach Reboot (MikroMCP `reboot`), dann:
# current-firmware muss 7.24 sein.
#
# REIHENFOLGE (bewusst): ap2 -> ap1 -> core -> branch.
# branch zuletzt, weil der Arbeitsplatz per LAN daran haengt — er ist die
# Lifeline zu allen anderen Geraeten und wird erst angefasst, wenn der Rest
# nachweislich sauber oben ist.
#
# Backups davor: <geraet>-post724-pre-fw-20260817.backup
:do { /system routerboard upgrade } on-error={ :log info "tp27: upgrade warf" }
:log info ("tp27: fw-geschrieben current=" . [/system/routerboard/get current-firmware] . " upgrade=" . [/system/routerboard/get upgrade-firmware] . " (current wechselt erst nach dem Reboot)")

View File

@@ -0,0 +1,63 @@
# TP28 — die toten Standalone-WLAN-Objekte vom CAP abraeumen.
#
# BEFUND (2026-08-18): ap1 und ap2 tragen weiterhin die komplette WLAN-Config aus
# dem TP3-Standalone-Aufbau — 2 channel, 6 datapath, 6 security, 7 configuration,
# 1 steering. Seit dem CAPsMAN-Cutover (2026-08-17) kommt all das vom Manager und
# wird auf dem CAP als DYNAMISCHES Interface angelegt; die statischen Objekte sind
# wirkungslos. Sie im Template als Soll zu rendern hiesse, totes Zeug zur Absicht
# zu erklaeren — dasselbe Argument, mit dem in TP26 die defconf-Firewallregeln der
# APs nicht ins Template geschrieben wurden.
#
# ⚠️ REIHENFOLGE IST PFLICHT: `configuration` referenziert security, datapath und
# channel. Wer die Referenzierten zuerst loescht, bekommt einen Fehler und laesst
# einen Teil-Zustand zurueck (Netmiko-Lehre aus TP5 Etappe 1).
#
# 🔴 `[find !dynamic]` WIRFT HIER EINEN SYNTAXFEHLER (2026-08-18 aufgeschlagen):
# `/interface wifi configuration` und Geschwister haben gar keine `dynamic`-
# Eigenschaft -- die gibt es nur auf `/interface wifi` selbst. Der erste Versuch
# lief deshalb komplett in die on-error-Zweige und aenderte NICHTS, meldete aber
# "Plan Completed". Wieder ein Beleg: nur die Gegenpruefung am Geraet zaehlt.
# Richtig ist hier schlicht `[find]` -- diese Objekte sind ausnahmslos statisch.
#
# ⚠️ Die dynamischen VAPs (`/interface wifi find dynamic`) werden NICHT angefasst.
# Sie tragen ihre Werte inline (datapath.vlan-id=..., SSID im Kommentar) und
# referenzieren kein lokales Objekt -- am Geraet gegengeprueft.
#
# Erwartung: die Client-Verbindungen bleiben unberuehrt, weil kein einziges dieser
# Objekte von einem laufenden VAP referenziert wird.
:do { /interface wifi configuration remove [find] } on-error={ :log info "tp28c: configuration warf" }
:do { /interface wifi steering remove [find] } on-error={ :log info "tp28c: steering warf" }
:do { /interface wifi security remove [find] } on-error={ :log info "tp28c: security warf" }
:do { /interface wifi datapath remove [find] } on-error={ :log info "tp28c: datapath warf" }
:do { /interface wifi channel remove [find] } on-error={ :log info "tp28c: channel warf" }
:log info ("tp28c: ENDE cfg=" . [:tostr [:len [/interface wifi configuration find]]] . " sec=" . [:tostr [:len [/interface wifi security find]]] . " dp=" . [:tostr [:len [/interface wifi datapath find]]] . " ch=" . [:tostr [:len [/interface wifi channel find]]] . " st=" . [:tostr [:len [/interface wifi steering find]]] . " cap-enabled=" . [:tostr [/interface wifi cap get enabled]])
# ---------------------------------------------------------------------------
# 🔴 NACHTRAG 2026-08-18, nach rund 14 h Ausfall von IOT + MEDIA:
# Die Annahme oben ("Client-Verbindungen bleiben unberuehrt") stimmte — das
# Skript hat den Ausfall NICHT ausgeloest. Die Bruchstelle liegt frueher, im
# CAPsMAN-Cutover selbst (2026-08-17): dabei wurde `/interface wifi cap
# slaves-datapath` nie gesetzt. Genau dieses Feld ist das EINZIGE, was die vom
# Manager erzeugten Slave-VAPs lokal in die Bridge haengt; der Manager schickt
# `datapath.bridge=bridge` zwar mit, der CAP uebernimmt davon aber nur die
# vlan-id. Seit dem Cutover assoziierten IOT-/MEDIA-Clients also sauber, ihre
# Frames gingen nirgendwo hin — am Core kam auf vlan30/vlan40 kein einziges
# Paket an. Die Master-Radios (wifi1/wifi2 = dirtyvader-main) sind statische
# Bridge-Ports und liefen weiter, deshalb sah das WLAN von aussen "halb ok" aus.
# Belege: `slaves-datapath` kommt in keiner Provisionierungs-Referenz vor
# (routeros/.secrets/ap*-prov-base.rsc), und die letzten IOT-/MEDIA-Leases
# stammen vom Abend des 2026-08-17.
#
# Dieses Skript hat den Schaden aber ZEMENTIERT: mit den geloeschten datapath-
# Objekten gab es lokal nichts mehr, worauf `slaves-datapath` haette zeigen
# koennen. Der naheliegende Ein-Zeilen-Fix war damit nicht mehr moeglich.
#
# Reparatur (ap1 und ap2, live 2026-08-18, seither im Template als Soll):
# /interface wifi datapath add name=dp-caps-slaves bridge=bridge
# /interface wifi cap set slaves-datapath=dp-caps-slaves
# Das Objekt traegt bewusst KEINE vlan-id — die kommt pro VAP vom Manager.
#
# LEHRE: `remove [find]` ohne Ruecksicht auf Referenzen aus /interface wifi cap.
# Vor dem Abraeumen pruefen: :put [/interface wifi cap get slaves-datapath]
# ---------------------------------------------------------------------------

97
routeros/tp3-capsman.rsc Normal file
View File

@@ -0,0 +1,97 @@
# ===== AS-BUILT 2026-06-17: TP3 WLAN/CAPsMAN — finale dirtyvader-SSIDs =====
# Angewendet via MikroMCP (run_command) auf ap1=10.40.41.1 (Manager+AP), ap2=10.40.41.2 (CAP).
# Ersetzt capsman-flat-asbuilt.rsc + ap1-manager.rsc + ap2-cap.rsc (alle obsolet).
#
# ZIEL: 5 sichtbare SSIDs, beide APs, beide Bänder, je SSID -> eigenes VLAN (wifi datapath vlan-id).
# dirtyvader-main(10) WPA3-only FT | dirtyvader-iot(30, NUR 2,4 GHz) WPA2+3
# dirtyvader-media(40) WPA2+3 FT | dirtyvader-untrusted(50) WPA2+3 | dirtyvader-guest(90) WPA2+3
# VERTAGT: hidden dirtyvader-infra(20) + dirtyvader-mgmt(99) — Mgmt verkabelt; ggf. TP8-Enterprise-SSID.
# Passphrase = gemeinsames TEMP, Platzhalter __TEMP_PSK__ vor dem Anwenden ersetzen.
# Echter Temp-Wert liegt (gitignored) in routeros/.secrets/tp3-capsman.psk; User ersetzt später pro security-Profil.
#
# KERNLEKTION (verifiziert 2026-06-17, RouterOS 7.23.1 wifi-Paket):
# - Der CAPsMAN-MANAGER kann seine EIGENEN (lokalen) Radios NICHT über Provisioning beziehen
# ("no connection to CAPsMAN, managed locally" / "SSID not set"). configuration.manager=capsman
# fällt auf 'local' zurück, ohne Config => kein SSID.
# - DAHER: ap1-Radios LOKAL zuweisen (Master via set, Slaves als statische virtual interfaces).
# ap2 (remote CAP) zieht Master+Slaves DYNAMISCH über die Provisioning-Regeln. -> Asymmetrisch,
# funktional identisch.
# - Bandzuordnung verifiziert: wifi1 = 5 GHz, wifi2 = 2,4 GHz (auf BEIDEN APs). iot nur auf 2,4 GHz.
# - reset = "/interface/wifi reset <if>" (NICHT 'reset-configuration' — existiert in 7.23 nicht).
# - CAPsMAN/CA-Cert + ap2-Adoption blieben unberührt (kein Re-Adopt).
# ================= ap1 = MANAGER + AP =================
# --- Channels (non-DFS gepinnt; diese Frequenzen gelten fuer ap2 = CAP, ap1 wird unten lokal ueberschrieben) ---
/interface/wifi/channel
add name=ch-2g band=2ghz-ax width=20mhz frequency=2462 ;# ch11 (ap2)
add name=ch-5g band=5ghz-ax width=20/40/80mhz frequency=5745 ;# ch149 non-DFS (ap2)
# --- Security je SSID (gemeinsames Temp-PW; FT nur main+media) ---
/interface/wifi/security
add name=sec-main authentication-types=wpa3-psk ft=yes ft-over-ds=yes passphrase="__TEMP_PSK__"
add name=sec-iot authentication-types=wpa2-psk,wpa3-psk ft=no passphrase="__TEMP_PSK__"
add name=sec-media authentication-types=wpa2-psk,wpa3-psk ft=no passphrase="__TEMP_PSK__" ;# ft=no: 802.11r bricht Sonos
add name=sec-untrusted authentication-types=wpa2-psk,wpa3-psk ft=no passphrase="__TEMP_PSK__"
add name=sec-guest authentication-types=wpa2-psk,wpa3-psk ft=no passphrase="__TEMP_PSK__"
# --- Datapath = SSID->VLAN (Bridge hat vlan-filtering=yes seit TP4) ---
/interface/wifi/datapath
add name=dp-main bridge=bridge vlan-id=10
add name=dp-iot bridge=bridge vlan-id=30
add name=dp-media bridge=bridge vlan-id=40
add name=dp-untrusted bridge=bridge vlan-id=50
add name=dp-guest bridge=bridge vlan-id=90
# --- Configuration je SSID (main band-spezifisch mit channel; Slaves ohne channel) ---
/interface/wifi/configuration
add name=cfg-main-5g ssid=dirtyvader-main mode=ap country=Austria security=sec-main datapath=dp-main channel=ch-5g
add name=cfg-main-2g ssid=dirtyvader-main mode=ap country=Austria security=sec-main datapath=dp-main channel=ch-2g
add name=cfg-iot ssid=dirtyvader-iot mode=ap country=Austria security=sec-iot datapath=dp-iot
add name=cfg-media ssid=dirtyvader-media mode=ap country=Austria security=sec-media datapath=dp-media
add name=cfg-untrusted ssid=dirtyvader-untrusted mode=ap country=Austria security=sec-untrusted datapath=dp-untrusted
add name=cfg-guest ssid=dirtyvader-guest mode=ap country=Austria security=sec-guest datapath=dp-guest
# --- CAPsMAN bleibt enabled (CA/Cert auto, ap2-Pairing unberührt) ---
# /interface/wifi/capsman set enabled=yes interfaces=bridge ;# war bereits aktiv
# --- Provisioning fuer ap2 (remote CAP): Master + Slaves; iot NUR in der 2-GHz-Regel ---
/interface/wifi/provisioning
add action=create-dynamic-enabled supported-bands=2ghz-ax master-configuration=cfg-main-2g \
slave-configurations=cfg-iot,cfg-media,cfg-untrusted,cfg-guest
add action=create-dynamic-enabled supported-bands=5ghz-ax master-configuration=cfg-main-5g \
slave-configurations=cfg-media,cfg-untrusted,cfg-guest
# --- ap1-EIGENE Radios LOKAL zuweisen (Manager provisioniert sich nicht selbst!) ---
/interface/wifi
set wifi1 configuration=cfg-main-5g configuration.manager=local disabled=no channel.frequency=5180 ;# wifi1 = 5 GHz, ch36 (override gg. ch-5g)
set wifi2 configuration=cfg-main-2g configuration.manager=local disabled=no channel.frequency=2412 ;# wifi2 = 2,4 GHz, ch1 (override gg. ch-2g)
# Statische Slaves je Band (5 GHz ohne iot):
add master-interface=wifi1 configuration=cfg-media name=w5-media disabled=no
add master-interface=wifi1 configuration=cfg-untrusted name=w5-untrusted disabled=no
add master-interface=wifi1 configuration=cfg-guest name=w5-guest disabled=no
add master-interface=wifi2 configuration=cfg-iot name=w2-iot disabled=no
add master-interface=wifi2 configuration=cfg-media name=w2-media disabled=no
add master-interface=wifi2 configuration=cfg-untrusted name=w2-untrusted disabled=no
add master-interface=wifi2 configuration=cfg-guest name=w2-guest disabled=no
# ================= ap2 = CAP (zieht alles vom Manager) =================
# Keine Objekte noetig — ap2 war bereits adoptiert (remote-cap state=Ok). Nach Provisioning-Update
# erscheinen dynamisch: wifi1(5G)=main + media/untrusted/guest ; wifi2(2,4G)=main + iot/media/untrusted/guest.
# Falls je noetig: /interface/wifi/capsman/remote-cap provision [find]
# ================= VERIFIKATION (durchgefuehrt 2026-06-18) =================
# ap1: monitor wifi1,wifi2 -> running, ch36/5180 (5G) + ch1/2412 (2,4G), tx-power ~18-25/15 dBm, 2 Clients FT-WPA3 VLAN10
# ap2: print brief -> 9 IFs MBR/DBR running, ch149/5745 (5G) + ch11/2462 (2,4G), "managed by CAPsMAN 10.40.41.1"
# ap1: /interface/wifi/capsman/remote-cap print -> ap2 STATE=Ok
# Kanaele distinkt + non-DFS auf beiden Baendern (Co-Channel beseitigt). KANAL-AENDERUNG an ap2 braucht
# manuellen Push: /interface/wifi/capsman/remote-cap provision [find]
# Offen (User-Geraetetest): Client je SSID -> DHCP aus 10.40.{10,30,40,50,90}.x, list_wifi_clients vlan-id.
# ================= OFFENE PUNKTE / TUNING =================
# - Echte Passphrasen pro security-Profil setzen (Temp ersetzen). User macht das selbst.
# - Kanal-Plan erledigt: ap1 ch36/ch1, ap2 ch149/ch11 (alle non-DFS, ueberlappungsfrei).
# - hidden dirtyvader-infra(20)/-mgmt(99) bei Bedarf nachziehen (sec WPA3-only, configuration.hide-ssid=yes).
# - Sonos-Migration (TP7-Anschluss): 7 WLAN-Sonos noch auf FB-WLAN (VLAN1) -> auf dirtyvader-media (VLAN40)
# holen, dann ist der Household geschlossen und mDNS-Repeater vlan10<->vlan40 liefert MAIN volle Discovery.
# - TP8: eine 802.1X-SSID (dirtyvader-secure) mit FreeRADIUS-Dynamic-VLAN -> dann PSK-main/infra retiren.

View File

@@ -0,0 +1,33 @@
# TP3.1 — ap2 VLAN-Trunk-Fix (As-built, 2026-07-01)
# Kontext: ap2 (CAP, local forwarding) hatte bridge vlan-filtering=no → datapath.vlan-id
# wurde nie auf die Leitung geschrieben → ALLE WLAN-Clients (main/media/iot/…) egressten
# ungetaggt in VLAN1 (defconf/10.40.41.x). Fix: ap2-Bridge VLAN-scharf machen wie ap1.
#
# WICHTIG (Lockout): ap2-Mgmt (10.40.41.2) liegt untagged auf VLAN1 über die Bridge; MCP/SSH
# läuft darüber. VLAN1 MUSS untagged=bridge,ether2 bleiben. Reihenfolge: erst Tabelle, dann Flip.
# Kein Scheduler auf ap2 (device-mode=home) → kein Auto-Revert; Recovery = Power-Cycle im OG.
#
# Diese Datei ist ap2-LOKAL (nicht CAPsMAN-verwaltet). Auf ap2 ausführen.
# Die master-Radios wifi1(5G)/wifi2(2.4G) tragen alle Client-VLANs getaggt; die Slave-VAPs
# (media/iot/untrusted/guest) forwarden über ihren Master → nur wifi1/wifi2 müssen tagged-Member sein.
# wifi1 (5G): main10 + media40 + untrusted50 + guest90
# wifi2 (2.4G): main10 + iot30 + media40 + untrusted50 + guest90
/interface bridge vlan
add bridge=bridge vlan-ids=1 untagged=bridge,ether2
add bridge=bridge vlan-ids=10 tagged=ether2,wifi1,wifi2
add bridge=bridge vlan-ids=30 tagged=ether2,wifi2
add bridge=bridge vlan-ids=40 tagged=ether2,wifi1,wifi2
add bridge=bridge vlan-ids=50 tagged=ether2,wifi1,wifi2
add bridge=bridge vlan-ids=90 tagged=ether2,wifi1,wifi2
# Flip als LETZTER Schritt:
/interface bridge set [find name=bridge] vlan-filtering=yes
# Danach auf ap1 einmalig re-provisionieren, damit der CAP sauber neu bindet:
# /interface wifi capsman remote-cap provision [find identity=ap2]
#
# Verifikation:
# /interface bridge vlan print detail → current-tagged je VLAN = ether2,wifi1(,wifi2)
# OG-Client-Test: Handy auf dirtyvader-main im OG → auf ap1 muss der Lease server=dhcp-v10
# (10.40.10.x) sein, NICHT defconf/10.40.41.x.

View File

@@ -0,0 +1,50 @@
# TP5 Etappe 5 — MAIN -> IOT aufmachen.
#
# BEFUND (User-Meldung 2026-08-19: Brother P750W und die Shellys aus VLAN10
# nicht erreichbar). Der Deny-by-default aus Etappe 3 arbeitet korrekt — es
# gab schlicht nie eine vlan10->vlan30-Zeile. Offen waren nur MAIN->MEDIA,
# MAIN->INFRA, MAIN->MGMT; IOT fehlte in der Liste.
#
# QUELLE: /opt/tp5-syslog/log/tp5-would-drop.log auf dem Docker-LXC,
# gefiltert auf "in:vlan10 out:vlan30". Aggregiert:
# 497 ICMP (Ping auf .30.5 und die Shellys)
# 489 udp 5353 mDNS
# 489 udp 137 NetBIOS
# 237 udp 3722 Apple-Continuity -> .30.201
# 40 udp 161 SNMP -> 10.40.30.5 (Brother P750W)
# Quellen: .10.183 (1596, Subnetz-Sweep), .10.180 (118), .10.82 (38).
#
# MERKREGEL AUS DEM LOG: der Drucker faellt mit udp/161 auf, NICHT mit 631.
# macOS fragt zuerst den SNMP-Status ab und bricht ab, bevor es zu IPP kommt.
# Wer nur 631 whitelistet, repariert nichts.
#
# ENTSCHEID (User, 2026-08-19): voll einbahnig, kein Portfilter.
# - MAIN ist das vertrauenswuerdige VLAN und steht zu INFRA/MEDIA/MGMT
# ohnehin schon vollstaendig offen — INFRA haelt NAS, Proxmox und
# Nautobot, also deutlich mehr Wert als die Shellys. MAIN->IOT eng zu
# filtern waere an dieser Stelle Symbolik, kostet aber bei jedem neuen
# IoT-Geraet eine Runde Fehlersuche.
# - Der Sicherheitswert der Segmentierung sitzt in der GEGENRICHTUNG:
# IOT->MAIN bekommt bewusst KEIN accept und bleibt gedroppt. Antworten
# auf Anfragen aus MAIN laufen ueber "defconf: accept est,rel,untracked",
# dafuer braucht es keine Regel.
#
# NEBENWIRKUNG, bewusst in Kauf genommen: der Subnetz-Sweep von .10.183
# (137/5353 an alle 254 Adressen) laeuft ab jetzt durch, statt im Drop-Log
# aufzulaufen. Betriebsneutral, aber der Log wird an dieser Stelle leiser —
# nicht mit "Problem geloest" verwechseln.
#
# INPUT-KETTE: unveraendert. "TP5-E4: MAIN->core admin" und
# "TP5: mDNS in vlan30" stehen bereits, der Core braucht nichts.
#
# BLOCKIERT NICHTS: ausschliesslich action=accept.
#
# Push: python3 nautobot/gc-manual-plan.py --device crs326-core \
# --commands-file routeros/tp5-e5-main-iot.rsc \
# --change-id TP5-E5-MAIN-IOT --deploy \
# --assert '/ip/firewall/filter?comment=TP5-E5: MAIN->IOT|count=1'
# ---------------------------------------------------------------------------
# 1) MAIN -> IOT, einbahnig. place-before die Log-Regel, damit sie vor dem
# Drop greift und der Log weiter zeigt, was uebrig bleibt.
:do { :if ([:len [/ip firewall filter find comment="TP5-E5: MAIN->IOT"]] = 0) do={ /ip firewall filter add chain=forward action=accept in-interface=vlan10 out-interface=vlan30 comment="TP5-E5: MAIN->IOT" place-before=[/ip firewall filter find comment="TP5: log-only, Vorstufe zum deny-by-default"] } } on-error={ :log info "tp5e5: 01 MAIN->IOT warf" }

View File

@@ -0,0 +1,74 @@
# TP5 Etappe 1 — Log-Lauf auf crs326-core. Blockiert NICHTS.
# Ausgerollt 2026-08-14 ueber Nautobot GC als manual Config Plans (Governance TP22),
# nicht per Direkt-Write. Backup davor: core-pre-tp5-etappe1-20260814.backup
#
# Diese Datei bildet den AUSGEROLLTEN Endzustand ab. Der Weg dorthin brauchte
# mehrere Anlaeufe; die Lehren stehen unten, damit Etappe 2-4 sie nicht wiederholen.
# Push: python3 nautobot/gc-manual-plan.py --device crs326-core \
# --commands-file routeros/tp5-etappe1-log-lauf.rsc --change-id TP5-Etappe1 --deploy
# --- 1) Syslog-Ziel ---------------------------------------------------------
# Der Core hat nur ~3 MB freien Flash -> Disk-Logging faellt aus (Platz + Flash-Verschleiss),
# und der Memory-Ring fasst 1000 Zeilen, den wickelt der Log-Lauf in Minuten um.
# Kollektor: syslog-ng auf dem Docker-LXC, 10.40.20.18:514 (Container `tp5-syslog`,
# Config /opt/tp5-syslog/config/syslog-ng.conf, Logs /opt/tp5-syslog/log/).
# ACHTUNG Name ohne Bindestrich — s. Lehre 1.
:do { /system logging action add name="tp5syslog" target=remote } on-error={ :log info "tp5: action add hat geworfen" }
:do { /system logging action set [find name="tp5syslog"] remote=10.40.20.18 remote-port=514 } on-error={ :log info "tp5: action set hat geworfen" }
# --- 2) firewall-Topic an den Kollektor -------------------------------------
:do { /system logging add action="tp5syslog" topics=firewall } on-error={ :log info "tp5: logging rule hat geworfen" }
# --- 3) firewall aus dem Memory-Ring nehmen ---------------------------------
# Sonst besteht der 1000-Zeilen-Puffer tagelang zu 100 % aus TP5-Zeilen und der
# On-Box-Log ist fuer jede andere Fehlersuche wertlos — genau dann, wenn man ihn braucht.
# Matcher live geprueft: topics=info trifft NICHT (Listen-Property), topics~"info" trifft genau 1.
# Rueckbau am Ende von Etappe 1: topics=info
/system logging set [find topics~"info" and action=memory] topics=info,!firewall
# --- 4) Die Log-Regel ans ENDE der forward-Kette ----------------------------
# action=log ist nicht-terminierend: das Paket laeuft weiter und faellt danach aus
# der Kette in die Default-Policy (accept). Es wird nichts blockiert.
/ip firewall filter add action=log chain=forward comment="TP5: log-only, Vorstufe zum deny-by-default" log-prefix="TP5-WOULD-DROP"
# --- 5) Einengung auf Nicht-WAN --------------------------------------------
# Der erste Lauf zeigte, dass die Regel auch saemtlichen Internet-Verkehr faengt
# (out:vlan100) — bei 242 Zeilen/min war ein Viertel davon WAN-bound, unter Last mehr.
# LAN->WAN ist eine GARANTIERTE Whitelist-Zeile (ohne sie hat kein VLAN Internet,
# auch die "Internet-only"-VLANs 50/90 nicht), also reines Rauschen fuer die Matrix,
# die Etappe 2 ableiten soll.
# 🔴 MERKPOSTEN FUER ETAPPE 3: der spaetere drop DARF NICHT bare ans Kettenende —
# davor gehoert `accept out-interface-list=WAN`, sonst ist das ganze Haus offline.
# Diese Zeile ist bekannt und muss nicht aus dem Log wiederentdeckt werden.
/ip firewall filter set [find comment="TP5: log-only, Vorstufe zum deny-by-default"] out-interface-list="!WAN"
# ===========================================================================
# LEHREN AUS DEM ROLLOUT (jede am Geraet belegt, nicht vermutet)
# ===========================================================================
#
# 1) 🔴 BINDESTRICHE IN `name=` WERDEN ABGELEHNT — der teuerste Fund.
# /system logging action add name="tp5-remote" target=remote -> wirft
# /system logging action add name="tp5-abc" target=remote -> wirft
# /system logging action add name="tp5syslog" target=remote -> geht
# Quoten hilft NICHT. Isoliert per :do/on-error-Plan mit Zaehler-Checkpoints.
# Zwischenzeitlich hatte ich das faelschlich der Reihenfolge `target=`/`remote=`
# im selben add-Statement zugeschrieben — diese Erklaerung ist WIDERLEGT
# (`set remote=... remote-port=...` in einem Statement laeuft anstandslos).
#
# 2) 🔴 EIN GESCHEITERTER NETMIKO-DEPLOY HINTERLAESST EINEN TEIL-ZUSTAND.
# Die TP22-Notiz "gescheiterter Deploy = sauberer Fehlschlag" galt dem NAPALM-Weg,
# der scheiterte, BEVOR er das Geraet anfasste. Netmiko schickt Zeile fuer Zeile
# und laesst Abgesetztes stehen: der erste Push hier hinterliess Zeile 3+4 auf dem
# Geraet, ohne 1+2. **Nach jedem Fehlschlag den Geraete-Ist-Stand pruefen.**
#
# 3) KEINE `print`-ZEILEN IN EINEN CONFIG PLAN. Ein `/system identity print` als
# Kanarienzeile hat die unmittelbar folgende Zeile verschluckt und eine
# Fehldiagnose erzeugt. Config Plans enthalten nur schreibende Kommandos.
#
# 4) SELBSTPROTOKOLLIERENDE PLAENE SIND DIE DIAGNOSE-METHODE DER WAHL.
# Der Deploy-Job liefert nur "E1028: found bad/failed/failure in output" und
# zeigt den Geraete-Output NICHT. Wickelt man jedes Kommando in
# `:do {...} on-error={ :log info "..." }` und setzt danach eine Zaehlzeile
# `:log info ("... count=" . [:tostr [:len [... find ...]]])`, liest man das
# Ergebnis anschliessend per get_log zeilengenau aus — ohne Direkt-Write.

View File

@@ -0,0 +1,32 @@
# TP5 Etappe 1 — PAUSE (User-Entscheid 2026-08-14).
# Grund: erst die .41.x -> .20.x-Migration fertigstellen (Prometheus-Scrape-Ziele,
# HA-pve-Integration). Daten, die davor gesammelt werden, gelten danach nicht mehr —
# 76 % des Inter-VLAN-Verkehrs waren genau dieses Altlast-Rauschen.
#
# Push: python3 nautobot/gc-manual-plan.py --device crs326-core \
# --commands-file routeros/tp5-etappe1-pause.rsc --change-id TP5-Etappe1-Pause --deploy
# 1) Log-Regel stilllegen. Bleibt als Objekt stehen (Kommentar = Idempotenz-Schluessel),
# das Wiederanschalten ist damit eine Zeile.
:do { /ip firewall filter set [find comment="TP5: log-only, Vorstufe zum deny-by-default"] disabled=yes } on-error={ :log info "tp5p: disable hat geworfen" }
# 2) Memory-Logging zurueckbauen. Der Ausschluss war nur wegen der Flut noetig;
# waehrend der Pause faellt keine an, und der On-Box-Log wird fuer die
# Migrationsarbeit wieder gebraucht.
:do { /system logging set [find topics~"info" and action=memory] topics=info } on-error={ :log info "tp5p: topics-restore hat geworfen" }
:log info ("tp5p: ENDE regel-disabled=" . [:tostr [/ip firewall filter get [find comment="TP5: log-only, Vorstufe zum deny-by-default"] disabled]] . " memory-topics=" . [:tostr [/system logging get [find topics~"info" and action=memory] topics]])
# ===========================================================================
# WIEDERAUFNAHME (Etappe 1 fortsetzen)
# ===========================================================================
# Der Kollektor laeuft weiter (Container `tp5-syslog` auf dem Docker-LXC,
# restart=unless-stopped, 10.40.20.18:514) und die Logging-Action `tp5syslog`
# samt Regel `topics=firewall` bleibt auf dem Core stehen — beides ist ohne die
# Filter-Regel wirkungslos. Zum Fortsetzen genuegen zwei Zeilen:
#
# /ip firewall filter set [find comment="TP5: log-only, Vorstufe zum deny-by-default"] disabled=no
# /system logging set [find topics~"info" and action=memory] topics=info,!firewall
#
# Vorher aufraeumen, sonst mischt sich Altes unter die neue Sammelphase:
# ssh root@10.40.20.100 'pct exec 400 -- sh -c ": > /opt/tp5-syslog/log/tp5-would-drop.log"'

View File

@@ -0,0 +1,26 @@
# TP5 Etappe 1 — WIEDERAUFNAHME nach der Pause vom 2026-08-14.
# Gegenstueck zu routeros/tp5-etappe1-pause.rsc.
#
# Der Grund fuer die Pause ist weg: die .41.x-Altlasten sind abgeraeumt
# (HA -> 10.40.20.100, pve .41.100 entfernt, Core .41.211 entfernt,
# Prometheus bereinigt, ioBroker korrigiert). Vorher waren 76 % des
# Inter-VLAN-Verkehrs `vlan20 -> bridge`, also genau dieses Rauschen.
#
# Push: python3 nautobot/gc-manual-plan.py --device crs326-core \
# --commands-file routeros/tp5-etappe1-resume.rsc \
# --change-id TP5-Etappe1-Resume --deploy
# Backup davor: core-pre-tp5-resume-20260815.backup
#
# ABNAHME danach: `out:bridge` im Kollektor-Log muss auf 0 stehen.
# Die Logdatei ist vor dem Scharfschalten geleert worden.
# 1) Log-Regel wieder scharf. Blockiert nichts: action=log ist nicht-terminierend,
# und out-interface-list="!WAN" haelt den Internet-Verkehr draussen.
:do { /ip firewall filter set [find comment="TP5: log-only, Vorstufe zum deny-by-default"] disabled=no } on-error={ :log info "tp5r: enable hat geworfen" }
# 2) firewall wieder aus dem Memory-Ring nehmen. Sonst besteht der 1000-Zeilen-Puffer
# waehrend der Sammelphase zu 100 % aus TP5-Zeilen und der On-Box-Log ist fuer
# jede andere Fehlersuche wertlos — genau dann, wenn man ihn braucht.
:do { /system logging set [find topics~"info" and action=memory] topics=info,!firewall } on-error={ :log info "tp5r: topics hat geworfen" }
:log info ("tp5r: ENDE disabled=" . [:tostr [/ip firewall filter get [find comment="TP5: log-only, Vorstufe zum deny-by-default"] disabled]] . " memory-topics=" . [:tostr [/system logging get [find topics~"info" and action=memory] topics]])

View File

@@ -0,0 +1,65 @@
# TP5 Etappe 2 — NACHTRAG. Vier Luecken, die der Kollektor nach dem ersten
# Etappe-2-Deploy gezeigt hat, plus eine Vorratsregel.
#
# QUELLE: /opt/tp5-syslog/log/tp5-would-drop.log auf dem Docker-LXC,
# Fenster 2026-08-17 15:00 .. 18:38 (nach dem E2-Deploy um 14:17:16).
# 119 Zeilen, aggregiert:
# 92 vlan30->vlan20 udp 10.40.30.227:5540 -> 10.40.20.105
# 18 vlan20->vlan99 tcp 10.40.20.31 -> .99.2/.99.11/.99.12:22
# 8 vlan99->vlan20 tcp .99.x:8728 -> 10.40.20.105
# 1 vlan30->vlan20 tcp 10.40.30.225:80 -> 10.40.20.105
#
# Die Behauptung "die Log-Regel ist verstummt" stammte aus einem 70-Sekunden-
# Fenster. Ueber dreieinhalb Stunden gemessen stimmt sie nicht. Das ist genau
# der Grund, warum das Runbook ein mehrtaegiges Fenster verlangt.
#
# BLOCKIERT NICHTS: ausschliesslich action=accept.
#
# Push: python3 nautobot/gc-manual-plan.py --device crs326-core \
# --commands-file routeros/tp5-etappe2-nachtrag.rsc \
# --change-id TP5-Etappe2-Nachtrag --deploy \
# --assert '/ip/firewall/filter?comment=TP5-E2: IOT->INFRA Matter|count=1'
# ---------------------------------------------------------------------------
# 1) MATTER. 92 der 119 Zeilen. 10.40.30.227 = SwitchBot-Stehlampe (DHCP-
# Reservierung "SwitchBot-floor-lamp"), die per Matter ueber udp/5540 an HA
# pusht. Die E2-Regel "IOT->INFRA Dienste" ist protocol=tcp — Matter faellt
# komplett durch. Ohne diese Zeile toetet Etappe 3 die Matter-Anbindung.
:do { :if ([:len [/ip firewall filter find comment="TP5-E2: IOT->INFRA Matter"]] = 0) do={ /ip firewall filter add chain=forward action=accept in-interface=vlan30 out-interface=vlan20 protocol=udp dst-port=5540 comment="TP5-E2: IOT->INFRA Matter" place-before=[/ip firewall filter find comment="TP5: log-only, Vorstufe zum deny-by-default"] } } on-error={ :log info "tp5n: 01 Matter warf" }
# 2) NAUTOBOT-DEPLOY-PFAD. Der Netmiko-Deploy kommt NICHT von 10.40.20.30
# (Web-Container), sondern von 10.40.20.31 = nautobot-worker. Beleg: die
# E2-Regel "Nautobot->MGMT ssh" auf .20.30 steht auf 0 Paketen, waehrend im
# Log 18 SYNs von .20.31 auf .99.{2,11,12}:22 stehen.
# Address-List statt Einzel-IP, weil der scheduler (.20.32) dieselben Jobs
# faehrt und ein Stack-Redeploy die Rollen tauschen kann.
# ⚠️ Ohne diese Zeile sperrt Etappe 3 den einzigen legitimen Config-Weg aus —
# also genau das Werkzeug fuer den Rueckzieher.
:do { :if ([:len [/ip firewall address-list find list=nautobot address=10.40.20.30]] = 0) do={ /ip firewall address-list add list=nautobot address=10.40.20.30 comment="nautobot web" } } on-error={ :log info "tp5n: 02a al-30 warf" }
:do { :if ([:len [/ip firewall address-list find list=nautobot address=10.40.20.31]] = 0) do={ /ip firewall address-list add list=nautobot address=10.40.20.31 comment="nautobot worker = der Deploy-Pfad" } } on-error={ :log info "tp5n: 02b al-31 warf" }
:do { :if ([:len [/ip firewall address-list find list=nautobot address=10.40.20.32]] = 0) do={ /ip firewall address-list add list=nautobot address=10.40.20.32 comment="nautobot scheduler" } } on-error={ :log info "tp5n: 02c al-32 warf" }
:do { :if ([:len [/ip firewall filter find comment="TP5-E2: Nautobot->MGMT ssh (list)"]] = 0) do={ /ip firewall filter add chain=forward action=accept src-address-list=nautobot out-interface=vlan99 protocol=tcp dst-port=22 comment="TP5-E2: Nautobot->MGMT ssh (list)" place-before=[/ip firewall filter find comment="TP5: log-only, Vorstufe zum deny-by-default"] } } on-error={ :log info "tp5n: 02d regel warf" }
# 3) RUECKRICHTUNG DES MGMT-PFADS. .99.x:8728 -> HA, als connection-state:new
# im Log. Das sind Pakete einer TCP-Sitzung, deren Conntrack-Eintrag
# abgelaufen ist, waehrend HA weiterpollt. Ein blanker Drop wuerde sie
# verschlucken und die mikrotik-Integrationen ins Timeout laufen lassen.
:do { :if ([:len [/ip firewall filter find comment="TP5-E2: MGMT->INFRA api-rueckweg"]] = 0) do={ /ip firewall filter add chain=forward action=accept in-interface=vlan99 out-interface=vlan20 protocol=tcp src-port=8728 comment="TP5-E2: MGMT->INFRA api-rueckweg" place-before=[/ip firewall filter find comment="TP5: log-only, Vorstufe zum deny-by-default"] } } on-error={ :log info "tp5n: 03 api-rueckweg warf" }
# 4) SHELLY-ANTWORT auf demselben Muster (tcp/80 -> HA, als new gesehen).
# Nur 1 Zeile im Fenster, aber dieselbe Ursache wie 3 und bei 22 gepollten
# Shellys jederzeit wieder faellig.
:do { :if ([:len [/ip firewall filter find comment="TP5-E2: IOT->INFRA http-rueckweg"]] = 0) do={ /ip firewall filter add chain=forward action=accept in-interface=vlan30 out-interface=vlan20 protocol=tcp src-port=80 comment="TP5-E2: IOT->INFRA http-rueckweg" place-before=[/ip firewall filter find comment="TP5: log-only, Vorstufe zum deny-by-default"] } } on-error={ :log info "tp5n: 04 http-rueckweg warf" }
# 5) VORRATSREGEL DNS -> AdGuard (10.40.20.16).
# AGH ist per Christophs Vorgabe eingerichtet, aber NICHT scharf geschaltet:
# die DHCP-Scopes liefern weiter 10.40.X.1 aus. Diese Zeile aendert daran
# nichts. Sie sorgt dafuer, dass das spaetere Umstellen ein reiner
# DHCP-Change bleibt und die dann scharfe Firewall nicht angefasst werden
# muss. Ohne sie waere die Reihenfolge eine Falle: DNS umgestellt, Drop
# scharf, Haus ohne Namensaufloesung.
:do { :if ([:len [/ip firewall filter find comment="TP5-E2: LAN->AGH dns (Vorrat)"]] = 0) do={ /ip firewall filter add chain=forward action=accept dst-address=10.40.20.16 protocol=udp dst-port=53 comment="TP5-E2: LAN->AGH dns (Vorrat)" place-before=[/ip firewall filter find comment="TP5: log-only, Vorstufe zum deny-by-default"] } } on-error={ :log info "tp5n: 05a dns-udp warf" }
:do { :if ([:len [/ip firewall filter find comment="TP5-E2: LAN->AGH dns-tcp (Vorrat)"]] = 0) do={ /ip firewall filter add chain=forward action=accept dst-address=10.40.20.16 protocol=tcp dst-port=53 comment="TP5-E2: LAN->AGH dns-tcp (Vorrat)" place-before=[/ip firewall filter find comment="TP5: log-only, Vorstufe zum deny-by-default"] } } on-error={ :log info "tp5n: 05b dns-tcp warf" }
# Zaehlzeile: 17 TP5-E2-Regeln erwartet (11 aus Etappe 2 + 6 hier).
:log info ("tp5n: ENDE e2-regeln=" . [:tostr [:len [/ip firewall filter find comment~"TP5-E2:"]]] . " nautobot-liste=" . [:tostr [:len [/ip firewall address-list find list=nautobot]]])

View File

@@ -0,0 +1,100 @@
# TP5 Etappe 2 — Whitelists auf crs326-core.
#
# Quelle: NICHT geraten, sondern aus 43.027 Zeilen Log-Lauf abgeleitet
# (Kollektor 10.40.20.18, Fenster 2026-08-16T17:21 .. 2026-08-17T14:05).
# Beobachtete Matrix (in -> out):
# 20682 vlan20->vlan99 8082 vlan10->vlan20 7123 vlan20->vlan10
# 6238 vlan20->vlan30 306 vlan30->vlan20 237 vlan10->vlan30
# 214 vlan10->vlan99 17 vlan10->vlan10 2 vlan30->vlan10
# 137 cf-mesh->{vlan20,vlan99}
# vlan40/50/90: NULL Inter-VLAN -> Internet-only ist risikofrei (D6).
#
# Entscheidungen (User 2026-08-17):
# D1 = MAIN->INFRA pauschal accept (MAIN ist das Trusted-VLAN, INFRA die
# eigene Serverzone; Least-Privilege bleibt bei IOT/UNTRUSTED/GUEST/MGMT).
# D4 = MGMT named sources: HA:8728 + Nautobot:22 + vlan10 + cf-mesh, Rest drop.
# D5 = IOT->INFRA: beobachtet ist nur 8123; MQTT 1883/8883 als dokumentierte
# Absicht mit aufgenommen (Shelly-Push kann jederzeit dazukommen).
#
# BLOCKIERT NICHTS: ausschliesslich action=accept. Der Drop kommt in Etappe 3.
#
# Push: python3 nautobot/gc-manual-plan.py --device crs326-core \
# --commands-file routeros/tp5-etappe2-whitelists.rsc \
# --change-id TP5-Etappe2-Whitelists --deploy
# Backup davor: core-pre-tp5-etappe2-20260817.backup
#
# ABNAHMEKRITERIUM: der Zaehler der Log-Regel muss danach (nahezu) stehen
# bleiben. Solange sie Nutzverkehr sieht, ist die Whitelist unvollstaendig —
# das ist das Gate fuer Etappe 3.
#
# ---------------------------------------------------------------------------
# WICHTIG — place-before: alle Zeilen muessen VOR die Log-Regel. Sonst laeuft
# die Log-Regel weiter voll und das Abnahmekriterium oben ist nicht messbar
# (action=log ist nicht-terminierend, funktional waere es egal - messbar nicht).
# Alle Adds sind per :if [:len [find comment=...]]=0 idempotent, ein zweiter
# Lauf legt also nichts doppelt an. Jede Zeile eigenes on-error (Lehre aus
# Etappe 1: ein gescheiterter Netmiko-Deploy hinterlaesst Teil-Zustand).
# ---------------------------------------------------------------------------
# 1) LAN -> WAN. Ohne diese Zeile nimmt der spaetere Drop das ganze Haus vom
# Netz, inklusive der "Internet-only"-VLANs 50/90. Bekannt, braucht keinen
# Logbeleg, und aus demselben Grund stand schon die Log-Regel auf "!WAN".
:do { :if ([:len [/ip firewall filter find comment="TP5-E2: LAN->WAN"]] = 0) do={ /ip firewall filter add chain=forward action=accept out-interface-list=WAN comment="TP5-E2: LAN->WAN" place-before=[/ip firewall filter find comment="TP5: log-only, Vorstufe zum deny-by-default"] } } on-error={ :log info "tp5e2: 01 LAN->WAN warf" }
# 2) Cloudflare Mesh (TP16.1/TP16.3). 137 Logtreffer belegen den Pfad.
# Ohne diese beiden Zeilen sperrt der Drop den kompletten Fernzugang aus —
# und zwar genau den, mit dem man den Fehler zuruecknehmen wollte.
:do { :if ([:len [/ip firewall filter find comment="TP5-E2: mesh->LAN"]] = 0) do={ /ip firewall filter add chain=forward action=accept in-interface=cf-mesh comment="TP5-E2: mesh->LAN" place-before=[/ip firewall filter find comment="TP5: log-only, Vorstufe zum deny-by-default"] } } on-error={ :log info "tp5e2: 02 mesh->LAN warf" }
:do { :if ([:len [/ip firewall filter find comment="TP5-E2: LAN->mesh"]] = 0) do={ /ip firewall filter add chain=forward action=accept out-interface=cf-mesh comment="TP5-E2: LAN->mesh" place-before=[/ip firewall filter find comment="TP5: log-only, Vorstufe zum deny-by-default"] } } on-error={ :log info "tp5e2: 03 LAN->mesh warf" }
# 3) D1 — MAIN <-> INFRA pauschal. Beobachtet waren 13 Dienste (macmini
# udp/3722 Continuity 6234x, NAS 6690 1578x, Nautobot 8080, AirPlay 7000,
# pve-mcp 3000, ssh auf pve/NAS/macmini, Ollama 11434, ...). Die
# Rueckrichtung braucht eine eigene Zeile, weil Continuity je Richtung
# eigene Flows oeffnet (7123 "new" aus vlan20).
:do { :if ([:len [/ip firewall filter find comment="TP5-E2: MAIN->INFRA"]] = 0) do={ /ip firewall filter add chain=forward action=accept in-interface=vlan10 out-interface=vlan20 comment="TP5-E2: MAIN->INFRA" place-before=[/ip firewall filter find comment="TP5: log-only, Vorstufe zum deny-by-default"] } } on-error={ :log info "tp5e2: 04 MAIN->INFRA warf" }
:do { :if ([:len [/ip firewall filter find comment="TP5-E2: INFRA->MAIN"]] = 0) do={ /ip firewall filter add chain=forward action=accept in-interface=vlan20 out-interface=vlan10 comment="TP5-E2: INFRA->MAIN" place-before=[/ip firewall filter find comment="TP5: log-only, Vorstufe zum deny-by-default"] } } on-error={ :log info "tp5e2: 05 INFRA->MAIN warf" }
# 4) INFRA -> IOT. HA (10.40.20.105) pollt ~22 Shellys auf tcp/80, 6238 Zeilen.
# Richtungsasymmetrisch gewollt: INFRA darf IOT anfassen, nicht umgekehrt.
:do { :if ([:len [/ip firewall filter find comment="TP5-E2: INFRA->IOT"]] = 0) do={ /ip firewall filter add chain=forward action=accept in-interface=vlan20 out-interface=vlan30 comment="TP5-E2: INFRA->IOT" place-before=[/ip firewall filter find comment="TP5: log-only, Vorstufe zum deny-by-default"] } } on-error={ :log info "tp5e2: 06 INFRA->IOT warf" }
# 5) D5 — IOT -> INFRA, nur Dienste. Beobachtet: 10.40.30.249 -> HA:8123 (203x).
# MQTT mit drin, weil die Doku es als Absicht nennt und ein Shelly-Push
# jederzeit dazukommen kann. Influx 8086 NICHT: nie beobachtet, und der
# Weg dorthin laeuft ueber HA.
:do { :if ([:len [/ip firewall filter find comment="TP5-E2: IOT->INFRA Dienste"]] = 0) do={ /ip firewall filter add chain=forward action=accept in-interface=vlan30 out-interface=vlan20 protocol=tcp dst-port=8123,1883,8883 comment="TP5-E2: IOT->INFRA Dienste" place-before=[/ip firewall filter find comment="TP5: log-only, Vorstufe zum deny-by-default"] } } on-error={ :log info "tp5e2: 07 IOT->INFRA warf" }
# 6) D4 — MGMT (vlan99). Drei named sources, sonst nichts:
# a) MAIN komplett = Arbeitsplatz (.10.82 -> ssh/80/443 auf branch/ap1/ap2)
# b) HA .20.105:8728 = mikrotik-Integration, 20.667 Polls. Ohne diese Zeile
# brechen die HA-Integrationen.
# c) Nautobot .20.30:22 = Netmiko-Deploy der Golden Config. Ohne diese Zeile
# sperrt sich TP22 selbst aus — der Weg, ueber den dieser Push laeuft.
# cf-mesh ist bereits durch Zeile 2 gedeckt.
:do { :if ([:len [/ip firewall filter find comment="TP5-E2: MAIN->MGMT"]] = 0) do={ /ip firewall filter add chain=forward action=accept in-interface=vlan10 out-interface=vlan99 comment="TP5-E2: MAIN->MGMT" place-before=[/ip firewall filter find comment="TP5: log-only, Vorstufe zum deny-by-default"] } } on-error={ :log info "tp5e2: 08 MAIN->MGMT warf" }
:do { :if ([:len [/ip firewall filter find comment="TP5-E2: HA->MGMT api"]] = 0) do={ /ip firewall filter add chain=forward action=accept src-address=10.40.20.105 out-interface=vlan99 protocol=tcp dst-port=8728 comment="TP5-E2: HA->MGMT api" place-before=[/ip firewall filter find comment="TP5: log-only, Vorstufe zum deny-by-default"] } } on-error={ :log info "tp5e2: 09 HA->MGMT warf" }
:do { :if ([:len [/ip firewall filter find comment="TP5-E2: Nautobot->MGMT ssh"]] = 0) do={ /ip firewall filter add chain=forward action=accept src-address=10.40.20.30 out-interface=vlan99 protocol=tcp dst-port=22 comment="TP5-E2: Nautobot->MGMT ssh" place-before=[/ip firewall filter find comment="TP5: log-only, Vorstufe zum deny-by-default"] } } on-error={ :log info "tp5e2: 10 Nautobot->MGMT warf" }
# 7) Same-VLAN mDNS. vlan10 -> vlan10 udp/5353, erzeugt vom mDNS-Repeater auf
# dem Core selbst (17 Zeilen). Erreicht die forward-Kette und wuerde von
# einem blanken Drop getroffen -> Discovery INNERHALB von MAIN kaputt.
:do { :if ([:len [/ip firewall filter find comment="TP5-E2: same-VLAN mDNS"]] = 0) do={ /ip firewall filter add chain=forward action=accept in-interface=vlan10 out-interface=vlan10 protocol=udp dst-port=5353 comment="TP5-E2: same-VLAN mDNS" place-before=[/ip firewall filter find comment="TP5: log-only, Vorstufe zum deny-by-default"] } } on-error={ :log info "tp5e2: 11 same-VLAN mDNS warf" }
# ---------------------------------------------------------------------------
# BEWUSST NICHT AUFGENOMMEN:
#
# vlan10 -> vlan30 (237x udp/3722) und vlan30 -> vlan10 (2x): das ist das
# iPhone 10.40.30.201, das im IOT-SSID haengt. D2 ist entschieden — es wird
# ins MAIN-SSID umgehaengt (Hands-on bei Christoph), dann verschwindet die
# Richtung von selbst. Eine Whitelist auf .30.201 waere ausserdem instabil:
# iOS vergibt pro SSID eine eigene Private Address.
# => Vor Etappe 3 pruefen, ob die Zeilen aus dem Log verschwunden sind.
#
# MAIN -> IOT ICMP (lanscan.app, D3): keine Dauerregel. Gehoert in die
# JIT-Admin-Gruppe (address-list mit timeout), Etappe 4.
# ---------------------------------------------------------------------------
# Zaehlzeile: 11 Whitelist-Regeln erwartet. Per get_log zeilengenau auslesen —
# der Deploy-Job zeigt den Geraete-Output nicht (Lehre aus Etappe 1).
:log info ("tp5e2: ENDE whitelist-regeln=" . [:tostr [:len [/ip firewall filter find comment~"TP5-E2:"]]] . " logregel-disabled=" . [:tostr [/ip firewall filter get [find comment="TP5: log-only, Vorstufe zum deny-by-default"] disabled]])

View File

@@ -0,0 +1,14 @@
# TP5 Etappe 3 — Auto-Revert abbestellen.
#
# Der Scheduler aus tp5-etappe3-drop.rsc haette den Drop nach 10 Minuten selbst
# wieder abgeschaltet. Die Abnahme ist durch, also wird er entfernt und der
# Drop bleibt stehen. Abnahme am 2026-08-18 (Drop live):
# INFRA->IOT Shelly .30.211:80 = 200
# INFRA->WAN cloudflare.com = 301
# INFRA->MAIN MacBook = erreichbar
# INFRA->MGMT branch:22 von pve = ZU (korrekt: pve ist keine Mgmt-Quelle)
# GC-Deploy nautobot-worker -> ap2:22 = SUCCESS (der Pfadbeweis)
# Gedroppt wurden in der Testphase 11 Pakete — ausschliesslich die eigene
# pve->branch:22-Sonde.
:do { /system scheduler remove [find name="tp5autorevert"] } on-error={ :log info "tp5e3: abbestellen warf" }
:log info ("tp5e3: AUTO-REVERT entfernt, drop-disabled=" . [:tostr [/ip firewall filter get [find comment="TP5: deny-by-default"] disabled]])

View File

@@ -0,0 +1,44 @@
# TP5 Etappe 3 — Deny-by-default am Ende von forward.
#
# WAS HIER PASSIERT
# -----------------
# Die forward-Kette endet heute nach der Log-Regel ohne Drop: alles, was keine
# Whitelist trifft, laeuft trotzdem durch. Diese Datei schliesst genau das.
# Der Drop kommt HINTER die Log-Regel, nicht an ihre Stelle — dann protokolliert
# der Kollektor weiter exakt das, was der Drop frisst. Ohne diese Sicht waere
# jede spaetere Fehlersuche Raten.
#
# VORBEDINGUNGEN, die am 2026-08-18 erfuellt sind:
# - 17 Whitelist-Regeln (11 aus Etappe 2 + 6 aus dem Nachtrag) stehen VOR der
# Log-Regel; Positionen 14..30, Log auf 31, live gegengeprueft.
# - Der Nachtrag hat die vier Luecken geschlossen, die der Kollektor nach dem
# ersten E2-Deploy gezeigt hat (Matter, nautobot-worker .20.31, die beiden
# Rueckwege). Ohne sie haette dieser Drop den GC-Deploy-Pfad selbst gekappt.
#
# ⚠️ REIHENFOLGE IM SKRIPT IST ABSICHT: erst der Auto-Revert-Scheduler, DANN der
# Drop. Andersherum gibt es ein Fenster, in dem der Drop scharf ist und die
# Sicherung noch nicht steht.
#
# 🛟 AUTO-REVERT: schaltet den Drop nach 10 Minuten selbst wieder ab und entfernt
# sich. Wer zufrieden ist, loescht den Scheduler von Hand — dann bleibt der Drop.
# Name OHNE Bindestrich: RouterOS lehnt "tp5-autorevert" ab (bekannte Falle,
# hat schon vier Deploys gekostet).
#
# Push: python3 nautobot/gc-manual-plan.py --device crs326-core \
# --commands-file routeros/tp5-etappe3-drop.rsc \
# --change-id TP5-Etappe3-Drop --deploy
# Backup davor: core-pre-tp5-etappe3-20260818.backup
#
# Rollback sofort: /ip firewall filter set [find comment="TP5: deny-by-default"] disabled=yes
# ---------------------------------------------------------------------------
# 1) Sicherung zuerst.
:do { :if ([:len [/system scheduler find name="tp5autorevert"]] = 0) do={ /system scheduler add name="tp5autorevert" interval=10m on-event="/ip firewall filter set [find comment=\"TP5: deny-by-default\"] disabled=yes; /system scheduler remove [find name=\"tp5autorevert\"]; :log warning \"tp5e3: AUTO-REVERT hat den Drop abgeschaltet\"" } } on-error={ :log info "tp5e3: 01 scheduler warf" }
# 2) Der Drop. Hinter die Log-Regel, damit der Kollektor weiterlaeuft.
# KEIN place-before: ein add haengt ans Kettenende an, und dort steht die
# Log-Regel bereits als letzte. Ein place-before auf die Log-Regel wuerde den
# Drop DAVOR setzen -- dann sieht der Kollektor nie wieder, was gedroppt wird.
:do { :if ([:len [/ip firewall filter find comment="TP5: deny-by-default"]] = 0) do={ /ip firewall filter add chain=forward action=drop comment="TP5: deny-by-default" } } on-error={ :log info "tp5e3: 02 drop warf" }
:log info ("tp5e3: ENDE drop=" . [:tostr [:len [/ip firewall filter find comment="TP5: deny-by-default"]]] . " autorevert=" . [:tostr [:len [/system scheduler find name="tp5autorevert"]]])

View File

@@ -0,0 +1,13 @@
# TP5 Etappe 4 — Auto-Revert abbestellen. Abnahme 2026-08-18 (input-Drop live):
# DNS aus VLAN20 an den Core core.bf.lan -> 10.40.99.1, heise.de -> 193.99.144.80
# AGH unveraendert nas.bf.lan -> 10.40.20.10
# MAIN->core admin 57 Pakete (MikroMCP/SSH laufen weiter)
# HA->core api 3 Pakete
# Die 5 mDNS-input-Regeln erstmals scharf: vlan30=87, vlan20=13 Pakete
# (standen seit dem Bau am 2026-08-14 auf 0)
# CAPsMAN ap1 + ap2 state=Ok
# Gedroppt wurde ausschliesslich Broadcast-Rauschen, fuer das der Core nie
# zustaendig war: MNDP (inzwischen explizit erlaubt), NetBIOS 137/138 aus
# VLAN20, SSDP-Announcements einer Sonos aus VLAN40.
:do { /system scheduler remove [find name="tp5e4autorevert"] } on-error={ :log info "tp5e4: abbestellen warf" }
:log info ("tp5e4: AUTO-REVERT entfernt, input-drop-disabled=" . [:tostr [/ip firewall filter get [find comment="TP5: input deny-by-default"] disabled]] . " seal-lan-disabled=" . [:tostr [/ip firewall filter get [find comment="TP10-seal-lan"] disabled]])

View File

@@ -0,0 +1,77 @@
# TP5 Etappe 4 — input-Kette haerten, MGMT isolieren, JIT-Admin-Gruppe.
#
# WARUM DAS DER RISKANTERE TEIL IST (riskanter als der Drop in forward)
# --------------------------------------------------------------------
# Der Core ist DNS- UND DHCP-Server fuer alle sieben VLANs. Beides ist
# input-Verkehr und wird heute allein von `TP10-seal-lan`
# (in-interface-list=LAN, accept) durchgewunken. Wer seal-lan naiv auf vlan10
# verengt, nimmt in derselben Sekunde jedem anderen VLAN DHCP und DNS.
# Deshalb strikt: erst die Ersatzregeln, dann seal-lan abschalten, dann Drop.
#
# GEPRUEFTE VORAUSSETZUNGEN (2026-08-18, live am Geraet):
# - /system ntp server = enabled:no -> die Clients nutzen den Core NICHT als
# Zeitquelle, es braucht also KEINE udp/123-Regel.
# - /ip service ist bereits auf 10.40.0.0/16 + 100.96.0.0/12 eingegrenzt;
# diese Kette ist die zweite, grobkoernigere Schranke davor.
# - Die 5 `TP5: mDNS in vlanXX`-Regeln stehen hinter seal-lan und zaehlen
# deshalb 0. Sobald seal-lan faellt, werden sie scharf — das ist Absicht
# und war beim Bau 2026-08-14 schon so vorgesehen.
#
# ⚠️ Wie in Etappe 3 gilt: erst Sicherung, dann Schnitt. Und die Log-Regel steht
# VOR dem Drop, damit der Kollektor zeigt, was der Drop frisst.
#
# Push: python3 nautobot/gc-manual-plan.py --device crs326-core \
# --commands-file routeros/tp5-etappe4-input-mgmt.rsc \
# --change-id TP5-Etappe4-Input --deploy
# Backup davor: core-pre-tp5-etappe4-20260818.backup
#
# Rollback sofort: /ip firewall filter set [find comment="TP10-seal-lan"] disabled=no
# /ip firewall filter set [find comment="TP5: input deny-by-default"] disabled=yes
# ---------------------------------------------------------------------------
# 0) Sicherung zuerst — schaltet BEIDE Schnitte zurueck, nicht nur einen.
:do { :if ([:len [/system scheduler find name="tp5e4autorevert"]] = 0) do={ /system scheduler add name="tp5e4autorevert" interval=10m on-event="/ip firewall filter set [find comment=\"TP5: input deny-by-default\"] disabled=yes; /ip firewall filter set [find comment=\"TP10-seal-lan\"] disabled=no; /system scheduler remove [find name=\"tp5e4autorevert\"]; :log warning \"tp5e4: AUTO-REVERT hat input-Drop und seal-lan zurueckgesetzt\"" } } on-error={ :log info "tp5e4: 00 scheduler warf" }
# 1) DNS. Der Core ist Resolver fuer jedes VLAN (dhcp-server network liefert
# ueberall 10.40.X.1 aus). Ohne diese zwei Zeilen ist nach dem Drop das
# ganze Haus ohne Namensaufloesung.
:do { :if ([:len [/ip firewall filter find comment="TP5-E4: dns udp"]] = 0) do={ /ip firewall filter add chain=input action=accept in-interface-list=LAN protocol=udp dst-port=53 comment="TP5-E4: dns udp" place-before=[/ip firewall filter find comment="TP10-seal-lan"] } } on-error={ :log info "tp5e4: 01a dns-udp warf" }
:do { :if ([:len [/ip firewall filter find comment="TP5-E4: dns tcp"]] = 0) do={ /ip firewall filter add chain=input action=accept in-interface-list=LAN protocol=tcp dst-port=53 comment="TP5-E4: dns tcp" place-before=[/ip firewall filter find comment="TP10-seal-lan"] } } on-error={ :log info "tp5e4: 01b dns-tcp warf" }
# 2) DHCP. 7 Server, 54 Reservierungen — alle haengen an udp/67 auf dem Core.
:do { :if ([:len [/ip firewall filter find comment="TP5-E4: dhcp"]] = 0) do={ /ip firewall filter add chain=input action=accept in-interface-list=LAN protocol=udp dst-port=67 comment="TP5-E4: dhcp" place-before=[/ip firewall filter find comment="TP10-seal-lan"] } } on-error={ :log info "tp5e4: 02 dhcp warf" }
# 3) MAIN = die Admin-Zone. Das ist das verengte seal-lan: ssh/www/winbox/api
# kommen ab jetzt nur noch aus vlan10 (plus Mesh, s. 6).
:do { :if ([:len [/ip firewall filter find comment="TP5-E4: MAIN->core admin"]] = 0) do={ /ip firewall filter add chain=input action=accept in-interface=vlan10 comment="TP5-E4: MAIN->core admin" place-before=[/ip firewall filter find comment="TP10-seal-lan"] } } on-error={ :log info "tp5e4: 03 main warf" }
# 4) HA pollt den Core selbst auf 8728. Live belegt: /ip service zeigt offene
# api-Verbindungen local=10.40.99.1 remote=10.40.20.105. Das ist input, nicht
# forward — die forward-Regel aus Etappe 2 deckt es NICHT ab.
:do { :if ([:len [/ip firewall filter find comment="TP5-E4: HA->core api"]] = 0) do={ /ip firewall filter add chain=input action=accept src-address=10.40.20.105 protocol=tcp dst-port=8728 comment="TP5-E4: HA->core api" place-before=[/ip firewall filter find comment="TP10-seal-lan"] } } on-error={ :log info "tp5e4: 04 ha warf" }
# 5) Nautobot deployt auch auf den Core selbst -> ssh als input. Address-List
# aus dem Etappe-2-Nachtrag (.20.30/.31/.32).
:do { :if ([:len [/ip firewall filter find comment="TP5-E4: Nautobot->core ssh"]] = 0) do={ /ip firewall filter add chain=input action=accept src-address-list=nautobot protocol=tcp dst-port=22 comment="TP5-E4: Nautobot->core ssh" place-before=[/ip firewall filter find comment="TP10-seal-lan"] } } on-error={ :log info "tp5e4: 05 nautobot warf" }
# 6) Der Fernzugang. Ohne diese Zeile sperrt der input-Drop den Mesh aus — also
# genau den Weg, ueber den man von auswaerts korrigieren wuerde.
:do { :if ([:len [/ip firewall filter find comment="TP5-E4: mesh->core admin"]] = 0) do={ /ip firewall filter add chain=input action=accept in-interface=cf-mesh comment="TP5-E4: mesh->core admin" place-before=[/ip firewall filter find comment="TP10-seal-lan"] } } on-error={ :log info "tp5e4: 06 mesh warf" }
# 7) Jetzt erst der Schnitt: seal-lan ausschalten (nicht loeschen — disabled ist
# der schnellere Rueckweg und der Auto-Revert greift genau hier an).
:do { /ip firewall filter set [find comment="TP10-seal-lan"] disabled=yes } on-error={ :log info "tp5e4: 07 seal-lan warf" }
# 8) Log VOR Drop, gleiche Doktrin wie in forward: der Kollektor zeigt weiter,
# was der Drop frisst. Ohne diese Zeile ist jede spaetere Fehlersuche Raten.
:do { :if ([:len [/ip firewall filter find comment="TP5: input log-only"]] = 0) do={ /ip firewall filter add chain=input action=log log-prefix="TP5-INPUT-WOULD-DROP" comment="TP5: input log-only" } } on-error={ :log info "tp5e4: 08 log warf" }
:do { :if ([:len [/ip firewall filter find comment="TP5: input deny-by-default"]] = 0) do={ /ip firewall filter add chain=input action=drop comment="TP5: input deny-by-default" } } on-error={ :log info "tp5e4: 09 drop warf" }
# 9) JIT-Admin-Gruppe (D3 aus dem Runbook). Dauerregel, LEERE Liste: solange
# niemand drinsteht, oeffnet sie nichts. Wer scannen oder ein IoT-Geraet
# anlernen will, traegt sich selbst mit timeout ein:
# /ip firewall address-list add list=jit-admin address=10.40.10.183 timeout=1h
# Das ersetzt die Dauerregel MAIN->IOT, die lanscan.app sonst braeuchte.
:do { :if ([:len [/ip firewall filter find comment="TP5-E4: JIT-Admin aus MAIN"]] = 0) do={ /ip firewall filter add chain=forward action=accept in-interface=vlan10 src-address-list=jit-admin comment="TP5-E4: JIT-Admin aus MAIN" place-before=[/ip firewall filter find comment="TP5: log-only, Vorstufe zum deny-by-default"] } } on-error={ :log info "tp5e4: 10 jit warf" }
:log info ("tp5e4: ENDE input-regeln=" . [:tostr [:len [/ip firewall filter find chain=input]]] . " seal-lan-disabled=" . [:tostr [/ip firewall filter get [find comment="TP10-seal-lan"] disabled]] . " input-drop=" . [:tostr [:len [/ip firewall filter find comment="TP5: input deny-by-default"]]] . " jit=" . [:tostr [:len [/ip firewall filter find comment="TP5-E4: JIT-Admin aus MAIN"]]])

View File

@@ -0,0 +1,32 @@
# TP5 Etappe 4 — NACHTRAG: Discovery-Protokolle in den input lassen.
#
# BEFUND (2026-08-18, unmittelbar nach dem input-Drop):
# Die input-Log-Regel fing MNDP-Broadcasts (udp/5678) von crs326-branch
# (04:F4:1C:B4:15:58), ap1 (D0:EA:11:4D:40:E0/E1) und ap2 (D0:EA:11:4D:40:39/3A).
#
# WARUM DAS NICHT HARMLOS IST:
# `/interface/wifi/cap` auf ap1 zeigt
# discovery-interfaces: vlan99
# current-caps-man-address: 04:F4:1C:B4:17:76%vlan99
# — eine MAC, keine IP. Die CAPs finden ihren Manager also per Layer-2-Discovery
# auf vlan99, nicht ueber eine konfigurierte Adresse. Ein Drop dieser Pakete
# bricht die Manager-Findung. Die BESTEHENDEN CAPWAP-Sitzungen laufen weiter
# (sie sind established und werden von TP10-seal-est akzeptiert), das Problem
# schlaegt also erst beim naechsten AP- oder Manager-Neustart zu — genau die
# Sorte Spaetschaden, die man im Log sehen und sofort schliessen will.
#
# Sicherheitlich billig: MNDP ist link-lokal (Broadcast, verlaesst das Segment
# nicht), CAPWAP wird auf vlan99 begrenzt.
#
# ⚠️ place-before ist Pflicht: ein blankes `add` haengt hinter den Drop und
# waere wirkungslos.
# ---------------------------------------------------------------------------
# MNDP. Aus der LAN-Liste, weil es auch die Winbox-Neighbors-Anzeige speist und
# als Broadcast ohnehin nicht routbar ist.
:do { :if ([:len [/ip firewall filter find comment="TP5-E4: MNDP discovery"]] = 0) do={ /ip firewall filter add chain=input action=accept in-interface-list=LAN protocol=udp dst-port=5678 comment="TP5-E4: MNDP discovery" place-before=[/ip firewall filter find comment="TP5: input log-only"] } } on-error={ :log info "tp5e4n: 01 mndp warf" }
# CAPsMAN/CAPWAP, eng auf das Mgmt-VLAN begrenzt.
:do { :if ([:len [/ip firewall filter find comment="TP5-E4: CAPsMAN capwap"]] = 0) do={ /ip firewall filter add chain=input action=accept in-interface=vlan99 protocol=udp dst-port=5246,5247 comment="TP5-E4: CAPsMAN capwap" place-before=[/ip firewall filter find comment="TP5: input log-only"] } } on-error={ :log info "tp5e4n: 02 capwap warf" }
:log info ("tp5e4n: ENDE mndp=" . [:tostr [:len [/ip firewall filter find comment="TP5-E4: MNDP discovery"]]] . " capwap=" . [:tostr [:len [/ip firewall filter find comment="TP5-E4: CAPsMAN capwap"]]])

View File

@@ -0,0 +1,19 @@
# TP7.1 — core ether13 (pve) um VLAN10 + VLAN40 erweitern.
#
# WOFUER: der SSDP-Relay fuer die macOS-Sonos-App braucht ein Bein in BEIDEN
# VLANs. SSDP ist link-lokales Multicast mit TTL 1: der M-SEARCH des MacBooks
# verlaesst VLAN10 nie, die ssdp:alive-NOTIFYs der Sonos verlassen VLAN40 nie.
# Ein Relay kann nur kopieren, was es selbst hoert — Routing hilft nicht, und
# `forward accept vlan10<->vlan40` ist laengst offen. Es scheitert VOR dem
# Verbindungsaufbau, an der Entdeckung.
#
# IST-STAND (live 2026-08-18): pve haengt an ether13, das nur `tagged vlan20`
# traegt (pvid=1). Tags hinzuzufuegen stoert VLAN20 nicht.
#
# ⚠️ tagged= wird VOLLSTAENDIG gesetzt, nicht angehaengt — die bestehenden
# Mitglieder ether1,ether8,ether15 stehen deshalb explizit mit drin. Ein
# `set tagged=ether13` allein wuerde die anderen entfernen und AP1 sowie den
# branch-Uplink aus dem VLAN werfen.
:do { /interface bridge vlan set [find vlan-ids=10 and !dynamic] tagged=ether1,ether8,ether13,ether15 } on-error={ :log info "tp71: vlan10 warf" }
:do { /interface bridge vlan set [find vlan-ids=40 and !dynamic] tagged=ether1,ether8,ether13,ether15 } on-error={ :log info "tp71: vlan40 warf" }
:log info ("tp71: ENDE v10-tagged=" . [:tostr [/interface bridge vlan get [find vlan-ids=10 and !dynamic] tagged]] . " v40-tagged=" . [:tostr [/interface bridge vlan get [find vlan-ids=40 and !dynamic] tagged]])

View File

@@ -0,0 +1,119 @@
# WAN-Cutover Runbook + Tutorial (ap1 / Liwest FB-Passthrough)
> ## 🟢 UPDATE 2026-06-25: jetzt der PERMANENTE Cutover (TP10 gekoppelt)
> Die Tunnel-Sperre ist gelöst: TP10 ist **cutover-ready**. Der permanente Ablauf (FM-FB → ap1-Apply → Passthrough →
> DDNS → Verify → alte Tunnel abbauen) steht in `docs/tp10-vpn-migration.md` + den Klick-Anleitungen
> `routeros/fritzbox-bf-bridge-cutover-klickanleitung.md` (BF-FB) und `routeros/fritzbox-wg-lanlan-klickanleitung.md` (FM-FB).
> ap1-Apply = **`routeros/tp10-cutover-apply.rsc`**. Das Folgende bleibt als **Test-/Diagnose- und Rollback-Referenz** gültig
> (Verkabelung, Konsolenpfad, `wantest-*`/`wanmac-*`, Sicherheitsnetze — alles weiterhin korrekt).
> ## ⚠️ Ursprünglich: REVERSIBLER TEST — die Reversibilität gilt weiter bis zum Tunnel-Teardown
> Die FritzBox bleibt **produktiver Router + VPN-Endpunkt + Telefon**. Wir testen nur, ob der
> Bridge/Passthrough-Mode sauber funktioniert, sammeln Diagnose-Daten und **bauen danach zurück**.
> **Grund:** Die BF-FritzBox terminiert aktuell **2 Site-to-Site-Tunnel** (AVM LAN-LAN: 1× IPsec +
> 1× WireGuard). Voller Bridge-Mode macht die FB zum reinen Modem → **die Tunnel sterben**. Permanenter
> Umstieg erst, wenn **TP10** diese Tunnel auf ap1 migriert hat (Ziel: 2× WireGuard, Hub auf ap1).
---
## Was ist `wantest-run`?
Ein **blockierendes Loop-Script auf ap1**, das 24×@15s (~6 min) die schichtweise WAN-Probe wiederholt
und persistent nach `wan-test.0.txt` loggt. Es ist der **autonome Mitschnitt**, der weiterläuft, während
Claude/MikroMCP während des Cutovers offline ist (kein Scheduler — device-mode blockt den; siehe unten).
Jede Probe misst: ether1-IP/Status, Default-Gateway, WAN-Erreichbarkeit (`:resolve`@1.1.1.1 = Routing+NAT
ohne DNS-Abhängigkeit) und DoH-Auflösung (= was die Clients als DNS nutzen).
---
## Verkabelung — was wohin (ap1 hAP ax²)
```
┌─────────────────────── ap1 (hAP ax²) ───────────────────────┐
Liwest ─ FritzBox ──────┤ ether1 = WAN ◀── HIER umstecken (an FB-Passthrough-Port) │
(Kabel) .254 │ ether2 = LAN-Trunk ◀── Uplink zu ALLNET — NIEMALS ZIEHEN! │
│ │ ether3/4/5 = frei │
│ └──────────────────────────────────────────────────────────────┘
│ (FB bleibt Router/DHCP/VPN/Telefon — flaches 10.40.41.0/24)
└─ FB-WiFi ─ dein Laptop ◀── Console-Pfad: erreicht ap1 auf 10.40.41.1
```
- **ether2 (LAN-Trunk → ALLNET):** der Management-/Uplink-Pfad. Läuft NICHT über ether1/FB-LAN4 →
übersteht den Cutover. **Auf keinen Fall ziehen** (sonst sind ap1 UND du vom Netz).
- **ether1 (WAN → FB):** der einzige Port, der in diesem Test umgesteckt wird.
## WiFi / Netz — welches wofür
| Zweck | Verbinde mit | Du landest in | ap1 erreichst du auf |
|---|---|---|---|
| **Console während Cutover** | **FritzBox-eigenes WiFi** | flach 10.40.41.0/24 | **`10.40.41.1`** (WebFig → New Terminal) |
| **DHCP-Funktionstest** | `install-10` … `install-99` (ap1-Radios, PW `jgp4ap3cwcnz`) | VLAN = SSID-Nummer, z.B. `install-10` → 10.40.10.x | Gateway `10.40.<vlan>.1`, Lease `.100–.200` |
> Die FB-WiFi-Console (10.40.41.1 via ether2) ist bewusst der Steuer-Pfad: unabhängig von ether1/LAN4,
> bleibt also stehen, egal was am WAN passiert.
---
## Schritt-für-Schritt
### 0 — Vorbereitung
- FB-WAN-MAC notiert: **`0C:72:74:36:90:99`** (CM-MAC, schon als Default im Clone-Script hinterlegt).
- Mit **FritzBox-WiFi** verbinden → WebFig `http://10.40.41.1` → **New Terminal**.
- Scripts sind installiert: `wantest-probe`, `wantest-run`, `wanmac-clone`, `wanmac-revert`.
### 1 — Auto-Capture starten (Terminal auf 10.40.41.1)
```
:global WTN 40 # optional: 40 Proben (~10 min). Ohne = Default 24 (~6 min)
/system script run wantest-run
```
→ läuft im Hintergrund weiter, auch wenn die Session abreißt. (Das Terminal blockiert während des Laufs —
einfach ein zweites WebFig-Fenster für den Log öffnen.)
### 2 — FritzBox umstellen + ether1 umstecken
- FB-UI → LAN4 auf Passthrough/IP-Client (FB-Klickanleitung aus dem Chat / `docs/`).
- **ether1** von ap1 an den FB-Passthrough-Port. **ether2 bleibt stecken!**
### 3 — Live mitlesen (zweites WebFig-Fenster)
```
/log print where message~"WANTEST"
```
Interpretation:
- `ether1 ... addr=86.x.x.x` → externe Liwest-IP gezogen ✓ (Bridge greift)
- `wan-reach via-1.1.1.1 ok ...` → **Routing+NAT ok** (ohne DNS). `FAIL` → WAN-Routing kaputt.
- `dns-doh ok ...` → DoH-Auflösung ok. `FAIL` → DNS/DoH kaputt = „nach .1 nichts" per Name.
### 4 — (optional) MAC-Clone testen, ~90 s, selbst-revertierend
```
/system script run wanmac-clone
```
→ setzt CM-MAC, release/renew, 4 Proben, dann **garantierter Auto-Revert** auf Werks-MAC.
Not-Revert sofort: `/system script run wanmac-revert`.
### 5 — ZURÜCKBAUEN (Pflicht — Test-Ende)
- FB-LAN4 wieder auf normal (Passthrough aus) → FB ist wieder voller Router.
- **ether1** zurück an den ursprünglichen FB-LAN-Port (Double-NAT-Zustand wie vorher).
- Damit laufen FB-VPN-Tunnel + Telefon ungestört weiter.
- `wantest-run` endet von selbst („WANTEST run done"). Früher stoppen:
`/system script job print` → `/system script job remove [find script=wantest-run]`.
### 6 — Auswerten
Claude wieder online holen → liest `wan-test.0.txt` → bestimmt Root-Cause für „nach .1 ging nichts mehr"
(Arbeitshypothese: DoH-Bootstrap, weil im Passthrough die FB als `dynamic-servers` wegfällt).
---
## Sicherheitsnetze (eingebaut)
- **Fallback-Route:** statische Default-Route via bridge (distance=2) bleibt — fällt ether1 aus, geht
Internet automatisch wieder über die FB (Double-NAT). Kein Totalausfall.
- **MAC-Auto-Revert:** im Clone-Lauf fest eingebaut (~90 s), läuft im Hintergrund garantiert zu Ende.
- **Console bleibt:** FB-WiFi → 10.40.41.1 via ether2 ist vom Cutover unabhängig.
## Notfall
- ether1 zieht keine externe IP / kein Internet: `/system script run wanmac-revert`, dann FB-LAN4 zurück
auf normal — Fallback-Route hält das LAN online.
- ap1 nicht erreichbar: ether2-Kabel prüfen (Uplink zu ALLNET), NICHT ether1.
- Hängendes Script killen: `/system script job print` → `/system script job remove <id>`.
## Hintergrund-Gotchas (ap1, RouterOS 7.23, device-mode=home)
- **scheduler + fetch blockiert** → Mechanismus ist bewusst scheduler-frei (Loop-Script).
- **`[/ping]` gibt nil zurück** und ICMP ist hier geblockt → Reachability via `:resolve`, nie ping.
- Quelle der Scripts: `routeros/wan-cutover-test.rsc`, `routeros/wan-macclone-test.rsc`.

View File

@@ -0,0 +1,58 @@
# ===== ap1 — Offline-fähiger WAN-Cutover-Test (scheduler-frei) =====
# RouterOS 7.23.x, device-mode=home. Schichtweise Diagnose für FB-Bridge/Passthrough-Cutover.
#
# ⚠️ device-mode blockiert /system scheduler (scheduler=false) UND /tool fetch (fetch=false).
# Daher: KEINE Scheduler — Autonomie via blockierendem Loop-Script (läuft im Hintergrund
# weiter, auch wenn die Console-Session abreißt). Raw-Internet-Test via :resolve gegen feste
# IP (ICMP/fetch sind hier unbrauchbar/geblockt).
#
# Apply: per REST/MikroMCP ODER /import wan-cutover-test.rsc
# Lesen: /log print where message~"WANTEST" bzw. Datei wan-test.0.txt (persistent)
# Einzel: /system script run wantest-probe
# Lauf: /system script run wantest-run (24 Proben @15s = ~6 min; Hintergrund-fähig)
# vorher optional: :global WTN 40 (mehr Iterationen)
# Stop: /system script job print → /system script job remove [find script=wantest-run]
# --- idempotent: alte Versionen entfernen ---
/system scheduler remove [find name~"^wantest-"]
/system script remove [find name~"^wantest-"]
/system logging remove [find action="wantest"]
/system logging action remove [find name="wantest"]
# --- persistentes Logging auf Disk (überlebt Offline-Fenster + Reboot) ---
# topics=script greift NUR auf script-generierte :log-Zeilen → genau unsere WANTEST-Zeilen
/system logging action add name=wantest target=disk disk-file-name=wan-test disk-lines-per-file=2000
/system logging add topics=script action=wantest
# --- Probe: eine schichtweise Messung, jede Zeile mit Timestamp + WANTEST-Prefix ---
/system script add name=wantest-probe policy=read,write,test,policy source={
:local ts ([/system clock get date] . " " . [/system clock get time])
:local e1stat "?"
:do { :set e1stat [/ip dhcp-client get [find interface=ether1] status] } on-error={}
:local e1addr "-"
:do { :set e1addr [/ip dhcp-client get [find interface=ether1] address] } on-error={}
:local e1mac [/interface ethernet get [find name=ether1] mac-address]
:log info ("WANTEST [" . $ts . "] ether1 status=" . $e1stat . " addr=" . $e1addr . " mac=" . $e1mac)
:local gw "-"
:do { :set gw [/ip route get [find dst-address="0.0.0.0/0" active=yes] gateway] } on-error={}
:log info ("WANTEST route default-gw=" . $gw)
# WAN-Routing+NAT: Router -> public 1.1.1.1 (UDP/53). UNABHÄNGIG von ap1-DNS-Config.
# (ICMP/HTTPS sind hier geblockt -> :resolve gegen feste IP ist das robuste Signal.)
: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?)" }
# DoH end-to-end: ap1-Resolver = was die Clients als DNS bekommen
:do { :local d [:resolve "google.com"] ; :log info ("WANTEST dns-doh ok google.com=" . $d) } on-error={ :log info "WANTEST dns-doh FAIL (DoH/Bootstrap?)" }
}
# --- Run: blockierender Loop, probt N×@15s. Hintergrund-fähig (überlebt Session-Abbruch). ---
/system script add name=wantest-run policy=read,write,test,policy source={
:global WTN
:if (([:typeof $WTN] = "nothing") or ($WTN = "")) do={ :set WTN 24 }
:log info ("WANTEST run start (" . $WTN . "x @15s, ~" . ($WTN * 15 / 60) . " min)")
:for i from=1 to=$WTN do={
/system script run wantest-probe
:delay 15s
}
:log info "WANTEST run done"
}
:log info "WANTEST cutover-test installiert (probe + run, scheduler-frei, disk-logging)"

View File

@@ -0,0 +1,57 @@
# ===== ap1 — WAN MAC-Clone-Test (FB CM-MAC → ether1), scheduler-frei =====
# RouterOS 7.23.x, device-mode=home. Setzt wan-cutover-test.rsc voraus (nutzt wantest-probe).
#
# FB-WAN-MAC = CM-MAC 0C:72:74:36:90:99 (DOCSIS-SW 7.3.5.3.521, vom User aus FB-UI).
# ⚠️ CM-MAC = Cable-Modem-/DOCSIS-Layer. Im echten Bridge-Modus nutzt die FB diese MAC
# selbst → MAC-Konflikt-Risiko. Public-IP kam bereits mit ether1-eigener MAC → Clone ist
# Verifikation, kein Muss.
#
# device-mode blockiert /system scheduler → Auto-Revert ist hier KEIN Scheduler, sondern fest
# in den Clone-Lauf eingebaut (linearer Ablauf, ~90 s Clone-Fenster, dann garantierter Revert —
# läuft im Hintergrund zu Ende, auch wenn die Console-Session abreißt).
#
# Ablauf (Console über altes WiFi auf 10.40.41.1):
# 1) Baseline-Cutover mit ether1-eigener MAC (wantest-run) → Public-IP geloggt
# 2) /system script run wanmac-clone → setzt CM-MAC, release/renew, 4 Proben, AUTO-REVERT
# 3) Log prüfen: IP/Schichten mit FB-MAC vs. Baseline (/log print where message~"WANTEST")
# Not-Revert jederzeit: /system script run wanmac-revert
# --- idempotent ---
/system scheduler remove [find name~"^wanmac-"]
/system script remove [find name~"^wanmac-"]
# --- Clone: orig sichern → CM-MAC → release/renew → 4 Proben → GARANTIERTER Auto-Revert ---
/system script add name=wanmac-clone policy=read,write,test,policy source={
:global FBMAC
:if (([:typeof $FBMAC] = "nothing") or ($FBMAC = "")) do={ :set FBMAC "0C:72:74:36:90:99" }
:global E1ORIG
:set E1ORIG [/interface ethernet get [find name=ether1] mac-address]
:log info ("WANTEST macclone: orig=" . $E1ORIG . " -> fb=" . $FBMAC . " (Auto-Revert in ~90s)")
/interface ethernet set [find name=ether1] mac-address=$FBMAC
:do { /ip dhcp-client release [find interface=ether1] } on-error={}
:delay 2s
:do { /ip dhcp-client renew [find interface=ether1] } on-error={}
:delay 25s
:for i from=1 to=4 do={ /system script run wantest-probe ; :delay 15s }
# --- garantierter Auto-Revert im selben Lauf (kein Scheduler nötig) ---
/interface ethernet reset-mac-address [find name=ether1]
:do { /ip dhcp-client release [find interface=ether1] } on-error={}
:delay 2s
:do { /ip dhcp-client renew [find interface=ether1] } on-error={}
:delay 15s
/system script run wantest-probe
:log info ("WANTEST macclone done -> revert auf factory-MAC (orig war " . $E1ORIG . ")")
}
# --- Not-Revert: sofort Werks-MAC zurück (falls man früher raus will) ---
/system script add name=wanmac-revert policy=read,write,test,policy source={
/interface ethernet reset-mac-address [find name=ether1]
:do { /ip dhcp-client release [find interface=ether1] } on-error={}
:delay 2s
:do { /ip dhcp-client renew [find interface=ether1] } on-error={}
:delay 10s
/system script run wantest-probe
:log info "WANTEST macclone manual-revert -> factory-MAC"
}
:log info "WANTEST macclone-test installiert (clone mit eingebautem Auto-Revert + manual-revert)"