Files
RouterOS/HANDOFF.md
T
KayandClaude Sonnet 5 c9ecd3a0a9 M9/M10: Einfach/Experte-Modus + Experte-Tab (generischer RouterOS-Zugriff)
M9: Einrichten-Wizard bekommt einen Einfach/Experte-Modusschalter
(ModeStepView). Einfach überspringt VLAN, erlaubt nur ein LAN-Netzwerk
ohne Isolation, Firewall-Grundschutz fest an.

M10: neuer "Experte"-Tab mit generischem Motor (RouterOSMenuItem,
RouterOSCommand.remove, ConnectionService.fetchMenuItems, freies
"eigener Menüpfad"-Feld) plus kuratierten Formularen mit Tooltips
(RouterOSSchemaCatalog) für Firewall/NAT/Mangle/Raw/Adress-Listen,
Interfaces, IP, VPN, WLAN, Queues, System, Werkzeuge.

Live gegen einen hEX-Testrouter verifiziert (erst per SSH, dann vom
Nutzer selbst in der App), dabei 7 reale Bugs gefunden und gefixt —
der wichtigste: RouterOS' SSH-CLI gibt bei fehlgeschlagenen Befehlen
Exit-Code 0 zurück, wodurch apply() app-weit Fehler verschluckte statt
sie zu melden. Danach ergänzt: Bestätigungsdialog vor Anlegen/Ändern
+ Auto-Backup vor dem ersten Experte-Tab-Schreibvorgang je Sitzung
(Angleichung an den Wizard), sowie ein Dauer-Editor (Tage/Std/Min/Sek)
für Lease-/Ablaufzeit-Felder statt Freitext.

Details zu allen Bugs/Fixes: HANDOFF.md, CHATLOG.md.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EW3r6rW1xCf6UT5jNvt6rn
2026-09-14 00:03:50 +02:00

34 KiB
Raw Blame History

RouterOS Assistant — Handoff

Stand: App-Icon fertig, alle Kern-Features (M1M6) gebaut und mindestens teilweise gegen zwei echte physische Mikrotik-Testgeräte verifiziert (ein generisches Gerät für M1M4, ein hEX-Router für M5/M6 — dabei ein kritischer Parser-Bug gefunden und gefixt, siehe unten). Danach noch mehrere Nutzer-Wünsche umgesetzt: Tooltips, wählbarer Backup-Ordner, Schnell-Backup im Verbinden-Tab, "Werkseinstellungen wiederherstellen", eigenes App-Icon.

Neu in dieser Session: Einrichten-Wizard hat jetzt einen Einfach/Experte- Modusschalter (M9), und ein komplett neuer "Experte"-Tab (M10) gibt Formularzugriff auf einen großen, aber bewusst kuratierten Ausschnitt von RouterOS (Firewall/NAT/Mangle/Raw/Adress-Listen mit vollen Tooltips, plus Interfaces/IP/VPN/WLAN/Queues/System/Werkzeuge, plus ein "eigener Menüpfad"-Feld für alles, was nicht kuratiert ist). Live gegen den hEX-Testrouter (RouterOS 7.24.2) verifiziert — erst per direktem SSH, dann zusätzlich vom Nutzer selbst in der laufenden App (VLAN anlegen, DHCP-Server mit Adress-Pool zuweisen, löschen) — dabei sieben echte Bugs gefunden und gefixt (Bug 713 unten, nach jedem Fund Release neu gebaut und deployt). Bug 10 ist der wichtigste Fund dieser Session: RouterOS' SSH-CLI gibt bei fehlgeschlagenen Befehlen trotzdem Exit-Code 0 zurück — betraf apply() app-weit, nicht nur den Experte-Tab (siehe unten). Release-Build unter /Applications/RouterOS Assistant.app ist auf aktuellem Arbeitsstand (alle M9/M10-Änderungen + alle Bugfixes deployt und vom Nutzer bestätigt) — noch nicht committed (siehe git status), letzter Commit ist weiterhin 97fc216.

Ziel

Native macOS-App (SwiftUI), die Laien per geführtem Interview-Wizard durch die häufigsten Mikrotik-RouterOS-Konfigurationen führt: Internet-Anschluss, Heimnetzwerk/DHCP, zusätzliche VLAN-Netzwerke, WLAN, Firewall-Grundschutz, plus Backup/Wiederherstellung. Vollständiger ursprünglicher Plan/Kontext: ~/.claude/plans/purrfect-puzzling-bunny.md (seither an vielen Stellen weiterentwickelt, siehe unten).

Setup / Bauen

cd RouterOS
xcodegen generate                 # erzeugt RouterOSAssistant.xcodeproj (nicht in git)
open RouterOSAssistant.xcodeproj  # oder direkt per xcodebuild

# Debug (Xcode Cmd+R empfohlen für manuelle/Live-Tests, siehe Gatekeeper-Hinweis unten)
xcodebuild -project RouterOSAssistant.xcodeproj -scheme RouterOSAssistant \
  -destination 'platform=macOS' build

# Nur kompilieren, nicht ausführen (umgeht den Gatekeeper-Hang, siehe unten)
xcodebuild -project RouterOSAssistant.xcodeproj -scheme RouterOSAssistant \
  -destination 'platform=macOS' build-for-testing

# Release + Deploy nach /Applications
xcodebuild -project RouterOSAssistant.xcodeproj -scheme RouterOSAssistant \
  -configuration Release build
rm -rf "/Applications/RouterOS Assistant.app"
cp -R "<DerivedData>/Build/Products/Release/RouterOSAssistant.app" \
  "/Applications/RouterOS Assistant.app"
killall Dock   # Icon-Cache auffrischen, falls sich nur das Icon geändert hat

xcodegen ist via Homebrew installiert. Projekt-Struktur/Targets/Package- Abhängigkeiten stehen in project.yml, das ist die Quelle der Wahrheit — nicht das generierte .xcodeproj von Hand editieren.

Git: lokales Repo, kein Remote (Nutzer hat aktuell keine Gitea-Instanz). .xcodeproj, DerivedData, .build, Firmware-Dateien (*.npk, *.cpgz) und Router-Backups (*.rsc, /Backups/) sind gitignored.

Architektur

RouterOSAssistant/
  App/RouterOSAssistantApp.swift       — 3 Tabs, teilen sich EINE ConnectionService-Instanz
  Core/
    Models/
      RouterOSCommand.swift            — eine Änderung, drei Operationen (.add/.set/.remove), zwei Renderer (CLI-Zeile / REST-JSON)
      WanConfig.swift, LanDhcpConfig.swift, VlanEntry.swift, WifiNetworkConfig.swift, FirewallConfig.swift  — bauen je RouterOSCommand-Listen
      DhcpServerCommandBuilder.swift   — geteilte "Adresse+Pool+Server+Netzwerk"-Logik (LAN + VLAN)
      RouterOSModels.swift             — Credentials, DeviceInfo, Interface, RouterOSError
      RouterOSMenuItem.swift           — generisches Item (id + [String:String] fields) für JEDEN RouterOS-Menüpfad (M10)
      RouterOSSchema.swift             — RouterOSFieldSchema/RouterOSMenuSchema/RouterOSMenuCategory: kuratiertes Formular-Schema fürs Experte-Tool (M10)
      RouterOSSchemaCatalog.swift      — die eigentliche Menü-Liste: Firewall-Familie voll kuratiert, Rest teils kuratiert/teils generisch (M10)
    Networking/
      RouterOSTransport.swift          — Protocol: connect/fetchDeviceInfo/fetchInterfaces/fetchFirewallRuleCounts/fetchMenuItems/apply/disconnect
      RestTransport.swift              — REST-API (RouterOS ≥7.1), Zertifikats-TOFU, .set via GET+PATCH (findItemID), .remove via GET+DELETE, fetchMenuItems (Array- oder Singleton-Objekt)
      SSHTransport.swift               — SSH-Fallback via Citadel, CLI-Text-Parsing, resetToFactoryDefaults(), eigene Hostkey-TOFU, fetchMenuItems (generisch, inkl. Singleton-Fallback)
      RouterOSCliParser.swift          — parst `/system resource print`, `/interface print terse`, und generisch jeden Menüpfad (parseGenericItems/parseSingletonItem, M10)
      CertificateTrustStore.swift / CertificateFingerprint.swift  — TOFU für REST-Zertifikate
      SSHHostKeyTrustStore.swift / SSHHostKeyFingerprint.swift    — TOFU für SSH-Hostkeys (M7)
    Services/
      ConnectionService.swift          — zentraler App-State: REST-zuerst-SSH-Fallback, hält credentials/interfaces/deviceInfo, plus generisches fetchMenuItems
      BackupService.swift              — Config-Export (`/export terse`) über eigene SSH-Verbindung, wählbarer Zielordner
      FactoryResetService.swift        — /system reset-configuration über eigene SSH-Verbindung ("Gefahrenzone")
      KeychainService.swift            — Passwort-Speicherung
  Features/
    Wizard/Steps/Connect/              — Verbinden-Tab (inkl. Schnell-Backup-Button nach erfolgreicher Verbindung)
    Wizard/Steps/Setup/                — Einrichten-Tab: Mode → Wan → Lan → (Vlan nur Experte) → Wifi → Firewall → Review/Apply
    Expert/                            — Experte-Tab (M10): ExpertView (Kategorie-/Menüliste + "eigener Pfad"), ExpertViewModel, ExpertMenuDetailView (Item-Liste + Add/Edit-Sheet)
    Backup/                            — Sicherungen-Tab (Ordner wählen, Gefahrenzone: Werkseinstellungen wiederherstellen)
  Resources/Assets.xcassets/AppIcon.appiconset/  — App-Icon "Signal Router" (16px1024px)
RouterOSAssistantTests/                — reine Unit-Tests (Command-Builder, CLI-Parser, Fallback-Logik via Mock-Transport)

Architekturprinzip RouterOSCommand (.add/.set): .add legt neue Einträge an (CLI ... add key=value, REST POST). .set ändert bestehende Einträge, per matchField/matchValue gefunden — CLI löst das inline über set [find field=value] ..., REST muss dafür erst per GET das Item suchen, seine RouterOS-interne .id auslesen, dann PATCH restPath/<id> schicken (RestTransport.findItemID). Eingeführt für WLAN (M5), weil Wireless-Interfaces schon vor jeder Konfiguration existieren. VLAN (M4) hat trotzdem weiterhin keine Port-Zuweisung (Access/Trunk) — technisch jetzt machbar, aber bewusste Scope-Entscheidung, nicht nachgezogen.

System-Aktionen (Backup, Werksreset) laufen immer über eine eigene SSH-Verbindung, unabhängig vom aktiven Live-Transport — BackupService und FactoryResetService folgen demselben Muster: es sind einmalige System-Kommandos, kein Menü-Item zum Hinzufügen/Ändern, passen also nicht ins RouterOSCommand-Modell und haben keine verifizierte REST-Entsprechung.

ConnectionService ist der einzige geteilte State. Alle drei Tabs bekommen dieselbe Instanz von der App-Ebene injiziert (kein @EnvironmentObject, explizite Übergabe im Init). SwiftUI-Falle: jede View, die connectionService-Felder liest, muss ihn selbst als @ObservedObject halten — nicht nur transitiv über ein anderes ViewModel erreichen (siehe Bug 1 unten).

Gefundene Bugs (chronologisch, alle gegen echte Geräte)

  1. ConnectView zeigte nie VerbindungsstatusconnectionService war nur über viewModel.connectionService erreichbar (zwei Ebenen tief verschachteltes ObservableObject), SwiftUI abonniert das nicht automatisch. Fix: ConnectView hält connectionService zusätzlich selbst als @ObservedObject.

  2. NIOSSHError.localizedDescription nutzlos — bridged auf generisches NSError ("error 1"). Fix: String(describing:).

  3. SSH scheiterte mit keyExchangeNegotiationFailure — RouterOS bietet nur Legacy-Algorithmen an, Citadel-Default enthält sie nicht. Fix: algorithms: .all.

  4. Citadel.SSHClient.CommandFailed zeigte nur den Exit-CodeexecuteCommand() verwirft die gesammelte Ausgabe bei Exit-Code ≠ 0. Fix: eigene Sammlung über executeCommandStream().

  5. Picker-Warnungen durch Platzhalter-InterfacenamenprepareDefaults() korrigiert jetzt WAN und LAN, läuft schon im init.

  6. Kritisch: CLI-Parser verlor 6 von 7 Interfaces auf einem hEX-RouterparseInterfaces/parseDeviceInfo splitteten nur auf "\n"; die SSH-Ausgabe dieses Geräts trennte Zeilen anders, sodass alle Interface-Zeilen zu einer verschmolzen — beim key=value-Parsen überschrieb jedes Feld den vorherigen Wert, übrig blieb nur das letzte Interface im Text (lo, Loopback) mit vermischten Werten. Folge: jede WAN-Interface-Referenz in den angewendeten Firewall-Regeln zeigte auf lo statt den echten WAN-Port — auf diesem Testgerät folgenlos, weil RouterOS bereits eine vollständige eigene Standard-Firewall (defconf) mitbrachte, die weiterhin schützte; auf einem Gerät ohne bestehende Firewall wäre das eine wirkungslose, aber als aktiv angezeigte Firewall gewesen. Fix: split(whereSeparator: \.isNewline) (robust gegen \n/\r/\r\n) + zusätzliches Sicherheitsnetz: Loopback-Interfaces werden in SetupView jetzt grundsätzlich aus allen WAN/LAN/VLAN- Auswahllisten gefiltert. Regressionstest mit echten \r\n-getrennten hEX-Daten in RouterOSCliParserTests.

  7. /ip service: Feld heißt available-from, nicht address — beim Kuratieren des Experte-Schemas geraten statt geprüft. Live gegen den hEX bestätigt: /ip service print zeigt tatsächlich available-from=; ein set ... address=... hätte den Zugriffsfilter stillschweigend nicht gesetzt. Schema korrigiert (RouterOSSchemaCatalog.swift).

  8. RouterOS 7.24.2 zeigt .id gar nicht in print terse, und Singleton-Menüs verweigern terse komplett — Annahme beim Bau des generischen Experte-Motors (M10) war, .id=*N sei Teil jeder print terse-Zeile (wie bei REST-JSON). Live-Test zeigte: RouterOS 7.24.2 liefert dort gar kein .id-Feld; Menüs mit genau einem Eintrag statt einer Liste (/ip dns, /system identity, /system clock, /system ntp client, /tool e-mail) lehnen terse sogar komplett ab ("bad parameter terse"). Fix: :put [<Menüpfad> find] liefert die echten internen IDs (z.B. *C;*1;*2) — live bestätigt, dass die Reihenfolge exakt der von print terse entspricht, damit per Position zuordenbar. Für Singleton-Menüs neuer RouterOSMenuSchema.isSingleton- Schalter: Fetch fällt auf normales print (Doppelpunkt-Format, parseSingletonItem) zurück, .set läuft ohne [find ...] direkt (matchField: "" als Sentinel in RouterOSCommand/beiden Transports). Beides live gegen /system identity durchgespielt (Name testweise geändert, Fehlertext exakt geprüft, zurückgesetzt).

  9. Cross-Referenz-Felder (z.B. DHCP-Server → Adress-Pool) waren reiner Freitext — Nutzer meldete: "kann den vorher angelegten Adress-Pool nicht in der Auswahl sehen". Betraf jedes Feld, das den Namen eines Eintrags aus einem anderen Menü referenziert (DHCP-Server→Pool, PPP-Benutzer→Profil, Scheduler→Skript, Firewall-Filter→Adress-Liste, WireGuard-Peer→Interface) — alle als .text statt als Auswahl gebaut. Fix: neuer Feldtyp RouterOSFieldSchema.Kind.menuItemPick(menuPath: restPath:valueField:), lädt beim Öffnen eines Menüs live die existierenden Namen aus dem referenzierten Menü (ExpertViewModel.loadCrossReferenceOptions) und zeigt sie als Picker. Auf alle fünf betroffenen Felder angewendet.

  10. Kritisch, app-weit: RouterOS' SSH-CLI gibt bei fehlgeschlagenen Befehlen Exit-Code 0 zurück — Nutzer meldete: DHCP-Server-Anlage zeigte keine Fehlermeldung, wurde aber auch nicht angelegt. Live reproduziert: /ip dhcp-server add ... auf einem Interface mit bereits bestehendem Server lieferte "failure: server or relay with such interface already exists" und Exit-Status 0; ein ungültiger action=-Wert lieferte "syntax error (...)", ebenfalls Exit-Status 0 (per expect-Skript mit catch wait result geprüft). SSHTransport. apply() warf bisher nur bei einem von Citadel gemeldeten Nicht-Null-Exit-Code — also nie. Betraf jeden .apply()-Aufruf über SSH app-weit, nicht nur den Experte-Tab: der komplette Einrichten- Wizard (WAN/LAN/VLAN/Firewall) lief bei jedem SSH-Schreibvorgang mit demselben blinden Fleck, seit M3. Fix: apply() behandelt jede nicht-leere Ausgabe eines add/set/remove-Befehls als Fehler — laut allen in dieser Session live getesteten Befehlsfamilien (15+) sind erfolgreiche mutierende Befehle immer still. Nach dem Fix live end-to- end bestätigt: komplette Kette VLAN→IP-Adresse→Pool→DHCP-Server→ DHCP-Netzwerk auf einem freien Interface angelegt, lief fehlerfrei durch, danach vollständig wieder entfernt.

  11. Bool-Felder im Experte-Schema als "true"/"false" statt "yes"/"no" geschrieben — Nutzer meldete nach Bug-10-Fix: VLAN-Anlage zeigte jetzt "Unerwartete Antwort vom Router: syntax error (line 1 column 30)". Live reproduziert mit der exakt von der App erzeugten Befehlszeile (disabled=false) — identischer Fehler; mit disabled=no lief es durch. RouterOS' CLI akzeptiert nur yes/no für Bool-Parameter, nicht true/false. Betraf praktisch jedes kuratierte Schema (23× "false", 4× "true" als defaultValue). Fix zweigleisig: ExpertViewModel.pendingCommand normalisiert jeden .bool-Feldwert vor dem Senden auf yes/no (Schutz unabhängig von der Quelle), zusätzlich Katalog-Strings selbst bereinigt. Live erneut end-to-end bestätigt (VLAN→IP→Pool→DHCP-Server mit disabled=no).

  12. Lösch-Buttons fehlten komplett im Experte-Tab — Nutzer: "mir fehlen komplett die Löschen-Buttons". Ursache: .swipeActions(...) ist eine iOS/iPadOS-Wischgeste ohne Entsprechung auf macOS-Listen — dort erscheint schlicht nichts, unabhängig vom Code. Fix: sichtbarer Papierkorb-Icon-Button je Zeile statt Wisch-Geste (nur bei Nicht-Singleton-Menüs, wo Löschen überhaupt Sinn ergibt). Lösch-Pfad (ID-Ermittlung + remove [find .id=...]) live gegen das vom Nutzer selbst angelegte VLAN nachvollzogen, funktioniert.

  13. Interface-Auswahl (.interfacePick) war einmalig beim Verbinden eingefroren — Nutzer legte ein VLAN an, danach fehlte es in der Interface-Auswahl beim Anlegen eines DHCP-Servers. Ursache: .interfacePick las aus ConnectionService.interfaces, nur einmal bei finishConnecting() befüllt, nie danach aktualisiert — anders als die neueren .menuItemPick-Felder (Pool/Profil/Skript), die bei jedem Menü-Öffnen frisch laden. Fix: neues ExpertViewModel.liveInterfaceNames, per /interface print terse bei jedem reloadItems() aktualisiert; availableInterfaces-Parameter- Durchreichung (ExpertViewExpertMenuDetailViewExpertItemEditView) komplett entfernt, da überflüssig. Danach vom Nutzer selbst in der App bestätigt: neu angelegtes VLAN erscheint sofort in der DHCP-Server-Interface-Auswahl.

Lehren: Citadel/NIOSSH-Fehler immer mit String(describing:) loggen, nie .localizedDescription. Jede View, die ein ObservableObject aus einem ViewModel liest, muss es selbst separat als @ObservedObject halten. Zeilen-Splitting bei SSH-Ausgaben grundsätzlich mit \.isNewline statt festem "\n", da sich das je nach Gerät/Version unterscheiden kann. Bei generischem RouterOS-Tooling (M10) nichts über CLI-Ausgabeformate annehmen, was nicht live geprüft ist — .id-Verhalten und terse-Support unterscheiden sich zwischen Menütypen und RouterOS-Versionen. RouterOS' SSH-Exit-Code ist kein verlässliches Erfolgssignal — Ausgabetext ist die einzige verlässliche Fehlerquelle über diesen Transport (Bug 10); REST ist davon nicht betroffen (prüft HTTP-Statuscodes korrekt). RouterOS' CLI will yes/no für Bools, nie true/false (Bug 11) — an einer einzigen Stelle normalisieren (pendingCommand) statt an jedem Schema-Eintrag hoffen, es richtig geschrieben zu haben. SwiftUI- Interaktionsmuster nicht plattform-blind übernehmen — .swipeActions sieht in der Doku harmlos aus, ist aber faktisch iOS/iPadOS-only (Bug 12). Jeder Zustand, der sich während der Nutzung ändern kann (Interface-Liste nach Anlegen eines VLANs), gehört live nachgeladen, nicht einmalig beim Verbinden gecacht (Bug 13) — dasselbe Prinzip wie crossReferenceOptions für Pool/Profil/Skript, nur nicht von Anfang an konsequent auf alle Auswahllisten angewendet.

xcodebuild test hängt — Gatekeeper, kein Code-Bug

xcodebuild ... test kann exakt beim App-Start für ~5 Minuten hängen, dann "The test runner hung before establishing connection.". Ursache (per log show, Filter auf syspolicyd/amfid): ad-hoc-signierte Binaries lösen bei syspolicyd einen Live-Netzwerk-Check (GK performScan) gegen Apples Gatekeeper-Server aus; jeder Neu-Build ändert den Binary-Hash und kann einen neuen, manchmal langsamen Check auslösen. Betrifft nur den CLI-Weg — Xcodes Cmd+R-Weg bekommt vom Debugger-Start eine Gatekeeper-Ausnahme. Deshalb: bei Hängern erst xcodebuild ... build-for-testing (kompiliert nur, kein App-Start, kein Gatekeeper-Trigger) für den Compile-Check nutzen, echte Testläufe/manuelle Verifikation dem Nutzer über Xcode überlassen statt CLI-Versuche zu wiederholen.

Bekannte Einschränkungen (bewusst, nicht vergessen)

  • Backup-Wiederherstellung fehlt komplett — Nutzer wies explizit darauf hin (2026-09-14): BackupService kann Backups nur erzeugen (createBackup) und auflisten (listBackups), es gibt keine Funktion, ein gespeichertes .rsc-Backup zurück auf den Router zu spielen. Einzige verwandte Funktion ist "Werkseinstellungen wiederherstellen" (FactoryResetService, Gefahrenzone im Sicherungen-Tab) — das ist RouterOS' eigener Werksreset, kein Zurück- spielen einer konkreten Sicherung. Noch nicht umgesetzt, für später vorgemerkt (siehe Nächste Schritte).
  • REST-Pfad ungetestet für Schreibvorgänge — auf beiden bisherigen Testgeräten war www-ssl (Port 443) aus, jeder Schreibtest lief über SSH. Der REST-apply()-Pfad (POST/PATCH, findItemID für .set) ist nur gegen Mocks getestet.
  • WLAN-.set-Pfad (M5) weiterhin ungetestet gegen echte Hardware — nur der "kein WLAN"-Zweig ist bestätigt (zwei Testgeräte, beide ohne WLAN-Chip). Sicherheitsprofil-Anlage + SSID/Passwort-.set auf einem echten /interface wireless-Interface noch nie live gelaufen.
  • Firewall-Regeln mit korrektem WAN-Interface noch nicht erneut bestätigt — der erste Live-Test (M6) lief technisch durch, traf aber wegen Bug 6 oben lo statt des echten WAN-Ports. Nach dem Parser-Fix wurde nur die Interface-Auswahl erneut bestätigt ("alle Interfaces auswählbar"), nicht aber ein erneuter Firewall-Apply mit korrektem Interface + Kontrolle der resultierenden Regeln.
  • Neuer RouterOS-WiFi-Treiber (/interface wifi, wifiwave2/802.11ax) nicht unterstützt — wird erkannt und im UI erklärt, aber nicht konfiguriert.
  • Kein garantiertes Auto-Rollback bei Verbindungsabbruch während "Jetzt anwenden" — nur Backup-vorher + Bestätigungspflicht.
  • App ist ad-hoc signiert, nicht notarisiert — beim ersten Start einer frisch nach /Applications kopierten Version zeigt macOS die "nicht verifizierter Entwickler"-Warnung (Rechtsklick → Öffnen nötig).
  • SSH-Hostkey-TOFU ungetestetbestätigt gegen echte Hardware: erste Verbindung zeigte den "Unbekannter SSH-Schlüssel"-Dialog, nach Bestätigen + Trennen + erneutem Verbinden lief es ohne Rückfrage durch.
  • Experte-Tool (M10): mehrere kuratierte Bereiche noch nicht gegen Hardware bestätigt — WLAN (Legacy- und neuer wifiwave2-Treiber: Testgerät hat keinen WLAN-Chip), Bonding und PPPoE-Client (beide riskant für die eigene Erreichbarkeit/brauchen echte ISP-Zugangsdaten) nur gegen RouterOS-Dokumentation kuratiert, nicht live geprüft. /ip address, /ip dhcp-client, /ip dns, /ip service nur lesend geprüft (Feldnamen bestätigt), keine Schreibtests — Risiko, sich selbst auszusperren (SSH/REST-Dienst) bzw. die WAN-Verbindung zu kappen.
  • Experte-Tool: einige RouterOS-Bereiche bewusst nur generisch, nicht kuratiert — Hotspot, Logging (beide mehrteilig, kein flaches Schema), CAPsMAN, L2TP-/OpenVPN-Server, OSPF/BGP-Routing. Grund: keine verifizierte Quelle für die genauen Feldnamen, lieber generischer Schlüssel/Wert- Zugriff als geratene Felder (siehe Bug 7 oben, warum das eine berechtigte Sorge ist).

M8: Mehrere LAN-Interfaces + Netzwerk-Isolation

Auf Nutzerwunsch: mehrere physische LAN-Interfaces je mit eigenem DHCP-Server (SetupViewModel.lanConfigs: [LanDhcpConfig], analog zum schon vorhandenen VLAN-Listen-Muster), plus ein Isolation-Toggle pro LAN-/VLAN-Eintrag ("Von anderen Netzwerken isolieren"). Bei aktivem Firewall-Grundschutz erzeugt FirewallConfig daraus paarweise Forward-Drop-Regeln zwischen jedem isolierten Netzwerk und allen anderen konfigurierten Netzwerken (beide Richtungen, Pair-Dedup bei gegenseitiger Isolation). Internetzugriff bleibt weiterhin global über die bestehende WAN-NAT-Logik, nicht pro Netzwerk abschaltbar (bewusst außerhalb Scope).

Nebenbei behoben: VlanStepView behauptete vorher im Hilfetext bereits Isolation vom Hauptnetzwerk, ohne dass eine einzige Firewall-Regel das durchsetzte — reiner Text ohne Wirkung. Jetzt ist Isolation ein echter, optionaler Schalter mit tatsächlicher Regel-Erzeugung.

Nur gegen Unit-Tests verifiziert (FirewallConfigTests: isoliertes Netzwerk gegen nicht-isoliertes, gegenseitige Isolation, keine Isolation). Noch nicht gegen echte Hardware getestet — bräuchte zwei getrennte Testnetze am selben Router, um die Drop-Regeln praktisch zu bestätigen.

M9: Einfach/Experte-Modus im Einrichten-Wizard

Nutzerwunsch: zwei Modi statt einem Wizard-Durchlauf für alle. Neuer erster Schritt SetupStep.mode (ModeStepView, Segmented Picker).

  • Einfach: WAN → LAN (nur ein Netzwerk, kein Hinzufügen/Entfernen, kein Isolations-Toggle) → WLAN → Firewall (Grundschutz fest an, kein Toggle) → Review. VLAN-Schritt wird komplett übersprungen.
  • Experte: unveränderter voller Flow (Multi-LAN, VLAN, Isolation, Firewall-Toggle) — das, was vorher der einzige Wizard-Pfad war.

Beim Umschalten auf Einfach werden evtl. im Expertenmodus gesetzte Werte zurückgesetzt (SetupViewModel.applySimpleModeConstraints). Navigation (goNext/goBack) von linearem SetupStep.rawValue+1 auf expliziten switch umgebaut (nötig wegen bedingtem VLAN-Skip). Nur gegen Unit-Tests

  • Compile/Test-Build verifiziert, nicht gegen echte Hardware (der Live-Test dieser Session lief komplett über direktes SSH, nicht über die App-UI).

M10: Experte-Tab — generischer RouterOS-Konfigurationszugriff

Nutzerwunsch: "Zugriff auf alle Funktionen, was RouterOS bietet ... alles was du findest, der komplette Umfang". RouterOS hat 300+ Menüs (siehe /console inspect / CLI-Referenz) — statt für jedes einzelne ein Swift-Modell zu bauen (unrealistisch), zweistufige Architektur:

  1. Generischer Motor (deckt technisch jeden Menüpfad ab): RouterOSMenuItem + RouterOSCliParser.parseGenericItems/ parseSingletonItem, RouterOSCommand.remove (neu, neben add/set), ConnectionService.fetchMenuItems + Transport-Implementierungen. Im UI: freies "eigener Menüpfad"-Eingabefeld — jeder RouterOS-Pfad ist erreichbar, auch ohne kuratiertes Schema, dann als rohes Schlüssel/Wert-Formular.
  2. Kuratierte Schemas (RouterOSSchemaCatalog) mit echten Formularfeldern, Tooltips, Erklärtext und Warnhinweisen: die komplette Firewall-Familie (Filter/NAT/Mangle/Raw/Adress-Listen — der explizit genannte Schwerpunkt), dazu Interfaces (Bridge, VLAN, WireGuard+Peers, PPPoE-Client, Bonding), IP (Adressen, Pools, DHCP-Server+Netzwerk, DHCP-Client, DNS, Verwaltungsdienste, Routen), VPN (PPP-Secret/Profile), WLAN (Legacy + neuer wifiwave2-Treiber + Sicherheitsprofile), Queues (Simple/Tree), System (Identity/Clock/NTP/Scheduler/Skripte/Nutzer), Werkzeuge (Netwatch/E-Mail). Bewusst nicht kuratiert (siehe oben): Hotspot, Logging, CAPsMAN, L2TP-/OVPN-Server, OSPF/BGP.

Live gegen den hEX-Testrouter verifiziert (Add→Print→Set→Remove- Zyklus je Familie, Router danach nachweislich unverändert — Config-Export vorher/nachher byte-identisch bis auf Zeitstempel): Firewall Filter/NAT/Mangle/Raw/Adress-Listen, VLAN, WireGuard+Peers, IP-Pool, DHCP-Server+Netzwerk, statische Route, PPP-Secret/Profile, Queue Simple, Scheduler+Skript, Netwatch, sowie der neue Singleton-Pfad (/system identity). Dabei zwei Bugs gefunden und gefixt, siehe Bug 7/8 oben. Nicht gegen Hardware getestet: WLAN (kein Chip im Testgerät), Bonding, PPPoE-Client (beide riskant/brauchen echte Zugangsdaten), Schreibzugriff auf /ip address//ip dhcp-client//ip dns/ /ip service (nur lesend geprüft, Risiko Selbstaussperrung/WAN-Ausfall).

Nachtrag (Nutzer-Feedback-Runde, gleiche Session): Nutzer meldete zwei reale Probleme beim gedanklichen/App-Testen des DHCP-Server-Formulars — Bug 9 (Adress-Pool-Feld ohne Auswahl) und Bug 10 (fehlgeschlagenes Anlegen ohne jede Fehlermeldung). Beide gefunden und gefixt (siehe oben). Bug 10s Fix wurde erneut end-to-end live bestätigt: komplette Kette VLAN → IP-Adresse → Pool → DHCP-Server → DHCP-Netzwerk auf einem freien Interface (statt des zuvor kollidierenden ether5) angelegt, kein Fehlertext (weil tatsächlich erfolgreich), anschließend vollständig wieder entfernt.

Zweiter Nachtrag — jetzt tatsächlich durch die App-UI getestet (Nutzer selbst, gleiche Session): Nutzer legte VLAN + DHCP-Server über den Experte-Tab live an, fand dabei drei weitere reale Bugs (11: yes/no, 12: fehlende Lösch-Buttons, 13: eingefrorene Interface-Auswahl, siehe oben) — alle gefunden, gefixt, nach jedem Fix Release neu gebaut und deployt, vom Nutzer direkt in der App erneut getestet. Damit ist die SwiftUI-Oberfläche des Experte-Tabs (VLAN anlegen, einem DHCP-Server zuweisen, Adress-Pool wählen, löschen) erstmals tatsächlich manuell durchgeklickt und vom Nutzer als funktionierend bestätigt — nicht mehr nur die zugrundeliegende RouterOS-Syntax per SSH. Bestätigt: RouterOS erlaubt nur einen DHCP-Server pro Interface — der Versuch, ihn direkt auf ether5 zu setzen, scheiterte zurecht (dort hängt bereits dhcp5 aus einer früheren Session), auf dem VLAN-Interface funktioniert es.

Dritter Nachtrag — Sicherheits-Angleichung ans übrige App-Verhalten: Nutzer fragte, ob Experte-Tab-Änderungen sofort geschrieben werden oder eine Bestätigung braucht — Antwort war "sofort, kein eigener Bestätigungs-Button" (nur Löschen hatte einen Dialog). Das wich vom Einrichten-Wizard ab (Übersicht + Pflicht-Backup vor "Jetzt anwenden"). Auf Wunsch angeglichen:

  • Bestätigungsdialog vor Anlegen/Ändern: "Anlegen"/"Speichern" öffnet jetzt zuerst einen .confirmationDialog mit dem exakten Befehl, erst ein zweiter Klick führt ihn aus (ExpertMenuDetailView).
  • Auto-Backup einmal pro Verbindung: ConnectionService. hasExpertToolBackedUpThisSession (zurückgesetzt bei disconnect()), ExpertViewModel.ensureSessionBackup() zieht vor dem allerersten Schreibvorgang im Experte-Tab automatisch ein Backup (wie der Wizard), aber nur einmal pro Sitzung, nicht vor jeder einzelnen Änderung (ein Experte-Tab-Durchgang sind oft viele kleine Edits hintereinander).

Vierter Nachtrag — Dauer-Editor: Nutzer fragte, in welcher Einheit die DHCP-Server-Lease-Zeit angegeben wird. Live geprüft: RouterOS- Zeitwerte sind Zahl+Einheit (s/m/h/d/w, kombinierbar wie 1d12h30m), eine bare Zahl gilt als Sekunden (bestätigt: 90 → intern 1m30s). Nutzerwunsch danach: "ein sinnvoll gestaltetes Auswahlfenster ... ein Tooltip wäre hilfreich" statt nur Tooltip-Text. Neuer Feldtyp RouterOSFieldSchema.Kind.duration + DurationFieldEditor (vier Stepper: Tage/Std/Min/Sek, parst bestehende Werte beim Öffnen, schreibt beim Ändern die RouterOS-Suffix-Form zurück) — angewendet auf DHCP-Server "Lease-Zeit" und Adress-Listen "Ablaufzeit". Nutzer bestätigte danach: "funktioniert".

Stand der Milestones

  • M1M4: Projektgerüst, Connect, Backup, WAN/LAN/DHCP, VLAN — gegen echtes Testgerät verifiziert.
  • M5: WLAN-Schritt — nur "kein WLAN"-Zweig verifiziert, .set-Pfad weiterhin ungetestet (kein WLAN-Chip auf beiden Testgeräten bisher).
  • M6: Firewall-Schritt — live angewendet, dabei Bug 6 gefunden+gefixt; Interface-Auswahl danach erneut bestätigt, Regel-Ergebnis mit korrektem WAN-Port aber noch nicht erneut kontrolliert.
  • Zusatzfeatures nach M6: Tooltips auf allen Konfigurationsfeldern, wählbarer Backup-Ordner (Standard: ~/Library/Application Support/...), Schnell-Backup-Button im Verbinden-Tab, "Werkseinstellungen wiederherstellen" (Gefahrenzone im Sicherungen-Tab, /system reset-configuration no-defaults=no), eigenes App-Icon ("Signal Router"-Motiv).
  • 🔶 M7: Härtung — SSH-Hostkey-TOFU fertig, gegen echte Hardware bestätigt (inkl. neuem "Trennen"-Button im Verbinden-Tab, der dafür nötig wurde). Fehlerzustände/Politur und REST-Schreibpfad-Verifikation gegen ein Gerät mit aktivem www-ssl stehen noch aus.
  • 🔶 M8: Mehrere LAN-Interfaces mit eigenem DHCP + Netzwerk-Isolation (eigene Firewall-Regeln pro LAN/VLAN) — gebaut, nur Unit-Test-verifiziert, noch nicht gegen echte Hardware getestet.
  • 🔶 M9: Einfach/Experte-Modus im Einrichten-Wizard — gebaut, Compile/ Unit-Test-verifiziert. UI (Modusumschalter selbst) noch nicht manuell durchgeklickt — nur M10s Experte-Tab wurde das (siehe M10).
  • M10: Experte-Tab (generischer RouterOS-Zugriff + kuratierte Firewall- und weitere Schemas) — Kern-Logik live gegen Hardware verifiziert, UI (VLAN anlegen/löschen, DHCP-Server mit Adress-Pool zuweisen) vom Nutzer selbst in der laufenden App bestätigt funktionierend — sieben reale Bugs dabei gefunden und gefixt (Bug 713), danach zusätzlich Bestätigungsdialog vor Anlegen/Ändern + einmaliges Auto-Backup pro Sitzung (Angleichung an den Wizard) sowie ein Dauer-Editor (Tage/Std/ Min/Sek statt Freitext) für Lease-/Ablaufzeit-Felder ergänzt, alles vom Nutzer bestätigt. Weiterhin ungetestet: WLAN (kein Chip im Testgerät), Bonding, PPPoE-Client (beide riskant/brauchen echte Zugangsdaten), Schreibzugriff auf /ip address//ip dhcp-client//ip dns/ /ip service (Risiko Selbstaussperrung/ WAN-Ausfall).

Nächste Schritte

  1. Den kompletten Wizard einmal neu durchlaufen (WAN/LAN/VLAN/WLAN/ Firewall) auf dem werksresetteten hEX und dabei das Firewall-Ergebnis mit korrektem WAN-Port per /ip firewall filter print / /ip firewall nat print kontrollieren (bisher nur die Interface- Auswahl nach dem Parser-Fix bestätigt, kein erneuter Apply).
  2. WLAN (M5) an einem Gerät mit echtem WLAN-Chip nachholen.
  3. Rest von M7: Fehlerzustände/Politur, REST-Schreibtest an einem Gerät mit aktivem www-ssl.
  4. M8 gegen echte Hardware testen: zwei LAN-Interfaces/VLANs anlegen, eins isoliert, anwenden, per /ip firewall filter print kontrollieren, dass die Drop-Regeln greifen und der Rest (Internetzugriff, nicht-isolierte Netzwerke) unangetastet bleibt.
  5. M9 UI (Einfach/Experte-Modusumschalter im Einrichten-Tab selbst) noch manuell durchklicken — M10s Experte-Tab wurde bereits vom Nutzer bestätigt (siehe oben), der Moduswechsel im Wizard noch nicht.
  6. M10: WLAN-Schemas (an Gerät mit WLAN-Chip), Bonding, PPPoE-Client (mit echten oder Test-ISP-Zugangsdaten) noch gegen Hardware verifizieren.
  7. dhcp5/pool5 auf ether5 sind Test-Überbleibsel aus einer früheren Session — mit dem Nutzer klären, ob die noch gebraucht werden oder aufgeräumt werden können (aktuell blockieren sie ether5 für einen neuen direkt-auf-dem-Port angelegten DHCP-Server).
  8. Nicht anrühren: test-vlan (VLAN-ID 20, ether5), testpool (192.168.90.10-250) und testdhcp (DHCP-Server auf test-vlan) sind echte, vom Nutzer selbst über die App angelegte Konfiguration aus dieser Session, keine Test-Artefakte zum Aufräumen.
  9. Optional, kleinere Politur: den neuen Dauer-Editor (RouterOSFieldSchema.Kind.duration) auch auf weitere Zeitwert-Felder anwenden, die bisher nur Text mit Beispiel-Tooltip sind — WireGuard- Peer "Keepalive", Scheduler "Intervall", Netwatch "Prüf-Intervall".
  10. Backup-Wiederherstellung bauen — vom Nutzer explizit gewünscht, noch nicht begonnen. Vermutlich: BackupService um eine restoreBackup(_:for:)-Funktion erweitern, die den Inhalt eines gewählten .rsc-Backups über SSH auf den Router einspielt (RouterOS kann .rsc-Skripte per /import file-name=... ausführen, dazu muss die Datei vorher aufs Gerät hochgeladen werden — Weg noch nicht verifiziert, vor dem Bauen live prüfen statt zu raten), plus ein "Wiederherstellen"-Button pro Eintrag im Sicherungen-Tab (BackupListView) mit Bestätigungsdialog (ähnlich der bestehenden Werksreset-Gefahrenzone).

Kein Gitea-Remote vorhanden — falls der Nutzer später eine Gitea-Instanz aufsetzt (z.B. selbst gehostet auf der vorhandenen OMV-NAS, siehe Aperto/-Projekt), Remote hinzufügen und pushen; bis dahin lokales Git.

/Applications/RouterOS Assistant.app ist der aktuell installierte Release-Build (Stand: Commit 008ee65, App-Icon "Signal Router"). Nach jeder weiteren Code-Änderung muss dieser Build-und-Kopier-Schritt (siehe oben) wiederholt werden, damit die installierte App aktuell bleibt.