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
34 KiB
RouterOS Assistant — Handoff
Stand: App-Icon fertig, alle Kern-Features (M1–M6) gebaut und mindestens teilweise gegen zwei echte physische Mikrotik-Testgeräte verifiziert (ein generisches Gerät für M1–M4, 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 7–13 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" (16px–1024px)
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)
-
ConnectView zeigte nie Verbindungsstatus —
connectionServicewar nur überviewModel.connectionServiceerreichbar (zwei Ebenen tief verschachteltes ObservableObject), SwiftUI abonniert das nicht automatisch. Fix:ConnectViewhältconnectionServicezusätzlich selbst als@ObservedObject. -
NIOSSHError.localizedDescriptionnutzlos — bridged auf generisches NSError ("error 1"). Fix:String(describing:). -
SSH scheiterte mit
keyExchangeNegotiationFailure— RouterOS bietet nur Legacy-Algorithmen an, Citadel-Default enthält sie nicht. Fix:algorithms: .all. -
Citadel.SSHClient.CommandFailedzeigte nur den Exit-Code —executeCommand()verwirft die gesammelte Ausgabe bei Exit-Code ≠ 0. Fix: eigene Sammlung überexecuteCommandStream(). -
Picker-Warnungen durch Platzhalter-Interfacenamen —
prepareDefaults()korrigiert jetzt WAN und LAN, läuft schon iminit. -
Kritisch: CLI-Parser verlor 6 von 7 Interfaces auf einem hEX-Router —
parseInterfaces/parseDeviceInfosplitteten 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 auflostatt 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 inSetupViewjetzt grundsätzlich aus allen WAN/LAN/VLAN- Auswahllisten gefiltert. Regressionstest mit echten\r\n-getrennten hEX-Daten inRouterOSCliParserTests. -
/ip service: Feld heißtavailable-from, nichtaddress— beim Kuratieren des Experte-Schemas geraten statt geprüft. Live gegen den hEX bestätigt:/ip service printzeigt tatsächlichavailable-from=; einset ... address=...hätte den Zugriffsfilter stillschweigend nicht gesetzt. Schema korrigiert (RouterOSSchemaCatalog.swift). -
RouterOS 7.24.2 zeigt
.idgar nicht inprint terse, und Singleton-Menüs verweigerntersekomplett — Annahme beim Bau des generischen Experte-Motors (M10) war,.id=*Nsei Teil jederprint 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) lehnentersesogar 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 vonprint terseentspricht, damit per Position zuordenbar. Für Singleton-Menüs neuerRouterOSMenuSchema.isSingleton- Schalter: Fetch fällt auf normalesprint(Doppelpunkt-Format,parseSingletonItem) zurück,.setläuft ohne[find ...]direkt (matchField: ""als Sentinel inRouterOSCommand/beiden Transports). Beides live gegen/system identitydurchgespielt (Name testweise geändert, Fehlertext exakt geprüft, zurückgesetzt). -
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
.textstatt als Auswahl gebaut. Fix: neuer FeldtypRouterOSFieldSchema.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. -
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ültigeraction=-Wert lieferte"syntax error (...)", ebenfalls Exit-Status 0 (perexpect-Skript mitcatch wait resultgeprü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. -
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; mitdisabled=nolief es durch. RouterOS' CLI akzeptiert nuryes/nofür Bool-Parameter, nichttrue/false. Betraf praktisch jedes kuratierte Schema (23×"false", 4×"true"alsdefaultValue). Fix zweigleisig:ExpertViewModel.pendingCommandnormalisiert jeden.bool-Feldwert vor dem Senden aufyes/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 mitdisabled=no). -
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. -
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:.interfacePicklas ausConnectionService.interfaces, nur einmal beifinishConnecting()befüllt, nie danach aktualisiert — anders als die neueren.menuItemPick-Felder (Pool/Profil/Skript), die bei jedem Menü-Öffnen frisch laden. Fix: neuesExpertViewModel.liveInterfaceNames, per/interface print tersebei jedemreloadItems()aktualisiert;availableInterfaces-Parameter- Durchreichung (ExpertView→ExpertMenuDetailView→ExpertItemEditView) 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):
BackupServicekann 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,findItemIDfü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-.setauf 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
lostatt 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
/Applicationskopierten Version zeigt macOS die "nicht verifizierter Entwickler"-Warnung (Rechtsklick → Öffnen nötig). SSH-Hostkey-TOFU ungetestet— bestä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 servicenur 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:
- 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. - 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
.confirmationDialogmit dem exakten Befehl, erst ein zweiter Klick führt ihn aus (ExpertMenuDetailView). - Auto-Backup einmal pro Verbindung:
ConnectionService. hasExpertToolBackedUpThisSession(zurückgesetzt beidisconnect()),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
- ✅ M1–M4: 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-sslstehen 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 7–13), 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
- 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 printkontrollieren (bisher nur die Interface- Auswahl nach dem Parser-Fix bestätigt, kein erneuter Apply). - WLAN (M5) an einem Gerät mit echtem WLAN-Chip nachholen.
- Rest von M7: Fehlerzustände/Politur, REST-Schreibtest an einem Gerät
mit aktivem
www-ssl. - M8 gegen echte Hardware testen: zwei LAN-Interfaces/VLANs anlegen, eins
isoliert, anwenden, per
/ip firewall filter printkontrollieren, dass die Drop-Regeln greifen und der Rest (Internetzugriff, nicht-isolierte Netzwerke) unangetastet bleibt. - 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.
- M10: WLAN-Schemas (an Gerät mit WLAN-Chip), Bonding, PPPoE-Client (mit echten oder Test-ISP-Zugangsdaten) noch gegen Hardware verifizieren.
dhcp5/pool5aufether5sind 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 sieether5für einen neuen direkt-auf-dem-Port angelegten DHCP-Server).- Nicht anrühren:
test-vlan(VLAN-ID 20, ether5),testpool(192.168.90.10-250) undtestdhcp(DHCP-Server auftest-vlan) sind echte, vom Nutzer selbst über die App angelegte Konfiguration aus dieser Session, keine Test-Artefakte zum Aufräumen. - 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". - Backup-Wiederherstellung bauen — vom Nutzer explizit gewünscht,
noch nicht begonnen. Vermutlich:
BackupServiceum einerestoreBackup(_: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.