diff --git a/CHATLOG.md b/CHATLOG.md index 9fdb9bd..2afb7fa 100644 --- a/CHATLOG.md +++ b/CHATLOG.md @@ -105,8 +105,96 @@ Fix → erneuter Test: (Citadel-Version war längst über einen gezielteren Weg gefunden), keine weitere Aktion nötig. - Nutzer: "speicher das chatlog ebenfalls mit" → dieses Dokument. +- Nutzer: "ok, genug für heute" → Session pausiert. + +## Fortsetzung: M5 (WLAN), M6 (Firewall), zweiter kritischer Bug + +- Nutzer: "machen wir weiter" → Rückfrage per AskUserQuestion, ob M5 + (WLAN) oder M6 (Firewall) zuerst — Antwort: M5. +- Vor dem WLAN-Bau Rückfrage zur Architektur: WLAN-Interfaces existieren + schon vor jeder Konfiguration (SSID/Passwort ändern = `set`, kein + `add`) — unser bisheriges Befehlsmodell konnte nur `add`. Zwei Optionen + vorgeschlagen: WLAN immer über SSH (wie Backup) vs. echtes `set` in + `RouterOSCommand` für REST+SSH einbauen. Nutzer wählte Option 2, mit + explizitem Zusatz: Geräte ohne WLAN müssen berücksichtigt werden. + → `RouterOSCommand` um `.add`/`.set`-Operationen erweitert (REST-Seite + braucht GET-vor-PATCH, `RestTransport.findItemID`), `WifiNetworkConfig` + gebaut, Geräte-ohne-WLAN-Erkennung + Hinweis auf nicht unterstützten + neuen WiFi-Treiber. +- Nutzer testete M5 gegen das echte hEX-Testgerät: "das Gerät hat kein + WLAN, wurde erkannt. Konfiguration lief sauber durch" — der + "kein WLAN"-Zweig war damit bestätigt (der eigentliche `.set`-Pfad + blieb ungetestet, mangels WLAN-Chip). +- Weiter mit M6 (Firewall) — vorab Rückfrage, ob zusätzlich vor dem + Anwenden die Anzahl bereits vorhandener Firewall-Regeln angezeigt + werden soll (extra Lese-Aufruf) oder nur ein Warntext reicht. Nutzer + wählte die Anzeige-Variante. M6 gebaut: opt-in (Standard aus, wie + VLAN), Mikrotik-Standard-Preset (NAT/Masquerade, established/related + erlauben, invalid verwerfen, unaufgeforderte WAN-Verbindungen blocken), + `place-before` mit aufsteigendem Index, damit neue Regeln vor + eventuell vorhandenen landen. +- Nutzer testete M6 live gegen das hEX-Testgerät und schickte die + tatsächliche `/ip firewall filter print` / `/ip firewall nat print`- + Ausgabe. **Kritischer Fund:** jede WAN-Interface-Referenz in den neuen + Regeln zeigte auf `lo` (Loopback) statt des echten WAN-Ports. Der + WAN-Schritt der App hatte laut Nutzer auch nur "lo" zur Auswahl + gezeigt. +- Debugging im Dialog: erst Verdacht auf PTY-Unterschiede (App nutzt + keinen Pseudo-Terminal-Kanal) — Nutzer testete auf Bitte hin einmal + interaktiv (`ssh` mit Terminal) und einmal non-interaktiv + (`ssh admin@IP "/interface print terse"`, genau wie die App es macht) + und schickte beide rohen Ausgaben. Beide waren identisch und korrekt + (alle 7 Interfaces sauber aufgelistet) — das widerlegte die + PTY-Theorie und bestätigte: der Fehler lag im eigenen Parser, nicht in + RouterOS' Ausgabe. +- Ursache gefunden: `RouterOSCliParser` splittete nur auf `"\n"`; auf + diesem Gerät wurden dadurch alle Interface-Zeilen zu einer + zusammengefasst, wodurch sich die key=value-Felder gegenseitig + überschrieben — übrig blieb nur das letzte Interface im Text (`lo`) + mit vermischten Werten. Fix: `split(whereSeparator: \.isNewline)` + + zusätzlicher Sicherheitsfilter (Loopback nie in WAN/LAN/VLAN-Listen + wählbar) + Regressionstest mit den echten hEX-Rohdaten. Aufräum-Befehle + für die fehlerhaften "lo"-Regeln an den Nutzer gegeben (Ausführung + bisher nicht bestätigt). +- Nutzer bestätigte nach dem Fix: "sieht gut aus, alle Interfaces sind + nun auswählbar" — die Interface-Auswahl ist damit erneut bestätigt, + ein erneuter Firewall-Apply mit korrektem WAN-Port + Kontrolle der + Regeln steht noch aus. + +## Nutzer-Wünsche nach M6 + +- "Tooltips zu den einzelnen Konfigurationen" + "Backup-Pfad + auswählbar machen" → `.help(...)`-Tooltips auf allen Feldern in + Connect- und allen Einrichten-Schritten; Sicherungen-Tab bekam einen + wählbaren Zielordner (nativer macOS-Ordnerdialog, Standard bleibt + `~/Library/Application Support/...`). +- "ich brauche ... nochmal eine Möglichkeit ein Backup zu erstellen" + (im Verbinden-Tab, direkt nach dem Verbinden) → Schnell-Backup-Button + im Verbinden-Tab ergänzt, inkl. "Zuletzt gesichert"-Anzeige für den + aktuellen Host. +- "gibt es eine Möglichkeit die Standardkonfiguration wiederherzustellen + ... prüfe das bitte" → bestätigt: RouterOS' eigenes + `/system reset-configuration no-defaults=no` stellt die + Werkskonfiguration wieder her (legt dabei selbst ein Backup an). Als + klar abgetrennte, rot markierte "Gefahrenzone" im Sicherungen-Tab + gebaut, mit Bestätigungsdialog, eigenem Vorab-Backup und automatischer + Trennung der App-Verbindung nach dem Auslösen (Router startet neu). +- "compile und deploye als Maxosx app" → Release-Build erzeugt und nach + `/Applications/RouterOS Assistant.app` kopiert (ad-hoc signiert, daher + beim ersten Start Rechtsklick→Öffnen nötig). +- "ich brauche noch ein Icon (Mikrotik) ... zeige mir Vorschläge" → + sechs eigenständige Icon-Konzepte (kein Nachbau von Mikrotiks + Logo, nur farblich angelehnt) als Artifact präsentiert. Nutzer wählte + Konzept 1 ("Signal Router"). Icon per AppKit/NSImage-Skript (kein + Homebrew nötig, ein zunächst gestarteter `brew install librsvg`-Versuch + kompilierte eine Abhängigkeit langwierig aus Quellcode und wurde + abgebrochen) in alle macOS-Größen gerendert, ins Xcode-Projekt + eingebunden, Release neu gebaut und deployt. +- "speichere alles weg, damit wir eventuell später weiter machen können" + → `HANDOFF.md` und dieses Chatlog auf den aktuellen Stand gebracht. ## Stand am Ende dieser Session Siehe `HANDOFF.md` für den vollständigen technischen Stand, offene -Punkte (M5 WLAN, M6 Firewall, M7 Härtung) und bekannte Einschränkungen. +Punkte (Firewall-Regel-Nachtest, WLAN-Hardware-Test, M7-Härtung) und +bekannte Einschränkungen. diff --git a/HANDOFF.md b/HANDOFF.md index f95c1dd..5e368ed 100644 --- a/HANDOFF.md +++ b/HANDOFF.md @@ -1,21 +1,21 @@ # RouterOS Assistant — Handoff -Stand: nach M6 (Firewall-Schritt). M1–M4 gegen ein echtes physisches -Mikrotik-Testgerät (RouterOS 7.24.2) verifiziert. M5: nur der -"kein-WLAN-erkannt"-Zweig ist bestätigt (siehe Einschränkungen unten). M6 -ist gebaut, Build + Test-Compile grün, aber **noch nicht gegen echte -Hardware angewendet** — der Nutzer verifiziert diesen Schritt direkt in -Xcode (Cmd+R), da `xcodebuild test` von der Kommandozeile aktuell an -einem macOS-Gatekeeper-Netzwerk-Check hängt (siehe eigener Abschnitt -unten, kein Code-Bug). +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. Release-Build liegt unter +`/Applications/RouterOS Assistant.app`. ## 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, (geplant: Firewall). -Vollständiger Plan/Kontext: `~/.claude/plans/purrfect-puzzling-bunny.md` -(ursprüngliche Architekturentscheidung — seither an mehreren Stellen +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 @@ -25,19 +25,30 @@ 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' test + -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 "/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 (`brew install xcodegen`). Projekt- -Struktur/Targets/Package-Abhängigkeiten stehen in `project.yml`, das ist die -Quelle der Wahrheit — nicht das generierte `.xcodeproj` von Hand editieren. +`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` sowie die vorhandenen Firmware-Dateien -(`*.npk`, `*.cpgz`) sind gitignored. +`.xcodeproj`, `DerivedData`, `.build`, Firmware-Dateien (`*.npk`, `*.cpgz`) +und Router-Backups (`*.rsc`, `/Backups/`) sind gitignored. ## Architektur @@ -51,154 +62,168 @@ RouterOSAssistant/ DhcpServerCommandBuilder.swift — geteilte "Adresse+Pool+Server+Netzwerk"-Logik (LAN + VLAN) RouterOSModels.swift — Credentials, DeviceInfo, Interface, RouterOSError Networking/ - RouterOSTransport.swift — Protocol: connect/fetchDeviceInfo/fetchInterfaces/apply/disconnect - RestTransport.swift — REST-API (RouterOS ≥7.1), Zertifikats-TOFU - SSHTransport.swift — SSH-Fallback via Citadel, CLI-Text-Parsing + RouterOSTransport.swift — Protocol: connect/fetchDeviceInfo/fetchInterfaces/fetchFirewallRuleCounts/apply/disconnect + RestTransport.swift — REST-API (RouterOS ≥7.1), Zertifikats-TOFU, .set via GET+PATCH (findItemID) + SSHTransport.swift — SSH-Fallback via Citadel, CLI-Text-Parsing, resetToFactoryDefaults() RouterOSCliParser.swift — parst `/system resource print` und `/interface print terse` CertificateTrustStore.swift / CertificateFingerprint.swift — TOFU nur für REST bisher Services/ ConnectionService.swift — zentraler App-State: REST-zuerst-SSH-Fallback, hält credentials/interfaces/deviceInfo - BackupService.swift — Config-Export (`/export terse`) über eigene SSH-Verbindung + 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 + Wizard/Steps/Connect/ — Verbinden-Tab (inkl. Schnell-Backup-Button nach erfolgreicher Verbindung) Wizard/Steps/Setup/ — Einrichten-Tab: Wan → Lan → Vlan → Wifi → Firewall → Review/Apply - Backup/ — Sicherungen-Tab -RouterOSAssistantTests/ — reine Unit-Tests (Command-Builder, CLI-Parser, Fallback-Logik via Mock-Transport) + 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) ``` -**Wichtiges Architekturprinzip (seit M5 erweitert):** `RouterOSCommand` -kennt zwei Operationen: `.add` (neuer Eintrag — CLI `... add key=value`, -REST `POST`) und `.set` (bestehenden Eintrag ändern, per `matchField`/ -`matchValue` gefunden — CLI `... set [find field=value] key=value` löst -das inline, REST muss dafür erst per GET das Item suchen, seine -RouterOS-interne `.id` auslesen, dann `PATCH restPath/` schicken, -siehe `RestTransport.findItemID`). Eingeführt für M5 (WLAN), weil -Wireless-Interfaces schon vor jeder Konfiguration existieren — kein -"add" möglich. VLAN (M4) hat trotzdem weiterhin keine Port-Zuweisung -(Access/Trunk): das bräuchte zusätzlich RouterOS Bridge-VLAN-Filtering, -technisch jetzt machbar, aber bewusst nicht rückwirkend nachgezogen -(Scope-Entscheidung, nicht mehr Architektur-Zwang). +**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/` 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). Achtung SwiftUI-Falle -(siehe unten): jede View, die `connectionService`-Felder liest, muss ihn -selbst als `@ObservedObject` halten — nicht nur transitiv über ein anderes -ViewModel erreichen. +`@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). -## Was während der Live-Tests kaputt war und wie es gefixt wurde - -Reihenfolge der Bugs, alle gegen echtes Testgerät gefunden, jeweils als -eigener Commit (`git log` zeigt Details): +## Gefundene Bugs (chronologisch, alle gegen echte Geräte) 1. **ConnectView zeigte nie Verbindungsstatus** — `connectionService` war nur über `viewModel.connectionService` erreichbar (zwei Ebenen tief - verschachteltes ObservableObject). SwiftUI abonniert automatisch nur - Objekte, die eine View direkt per `@StateObject`/`@ObservedObject` hält. - Fix: `ConnectView` hält `connectionService` jetzt zusätzlich selbst. -2. **NIOSSHError.localizedDescription ist nutzlos** — bridged auf - generisches NSError (`"error 1"`), echte Diagnose steckt in - `CustomStringConvertible`. Fix: `String(describing:)` statt - `.localizedDescription`. -3. **SSH-Verbindung scheiterte mit `keyExchangeNegotiationFailure`** — - RouterOS bietet nur Legacy-Algorithmen (`diffie-hellman-group14-sha1`, - RSA-Hostkeys) an, Citadels Default-Algorithmenliste enthält die nicht. - Fix: `algorithms: .all` beim `SSHClient.connect(...)`. + 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-Code** — - Citadels `executeCommand()` verwirft die gesammelte Ausgabe, sobald der - Exit-Code ≠ 0 ist — genau der Text, den RouterOS bei einem Fehler - zurückgibt. Fix: eigene Sammlung über `executeCommandStream()` in - `SSHTransport.run(_:)`, damit die Ausgabe bis zum Fehlschlag erhalten - bleibt. -5. **Picker-Warnungen** ("selection is invalid and does not have an - associated tag") — `wanConfig`/`lanConfig` starteten mit festen - Platzhaltern (`"ether1"`/`"bridge"`), die nicht zu den echten - Interface-Namen des Geräts passen mussten. Fix: `prepareDefaults()` - korrigiert jetzt beide anhand eines "ist der Wert überhaupt in der - Liste"-Checks, läuft schon im `init`. + `executeCommand()` verwirft die gesammelte Ausgabe bei Exit-Code ≠ 0. + Fix: eigene Sammlung über `executeCommandStream()`. +5. **Picker-Warnungen durch Platzhalter-Interfacenamen** — `prepareDefaults()` + korrigiert jetzt WAN und LAN, läuft schon im `init`. +6. **Kritisch: CLI-Parser verlor 6 von 7 Interfaces auf einem hEX-Router** — + `parseInterfaces`/`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`. -**Lehre für weitere Arbeit:** Citadel/NIOSSH-Fehler grundsätzlich mit -`String(describing:)` loggen, nie `.localizedDescription` vertrauen. Jede -neue View, die ein ObservableObject aus einem ViewModel liest, muss es -selbst separat als `@ObservedObject` halten. +**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. ## `xcodebuild test` hängt — Gatekeeper, kein Code-Bug -Bei M6 hing `xcodebuild ... test` dreimal reproduzierbar exakt beim -App-Start für ~5 Minuten, dann `"The test runner hung before establishing -connection."`. Ursache laut `log show` (Filter auf `RouterOSAssistant`/ -`syspolicyd`/`amfid`): `amfid` markiert das ad-hoc-signierte Binary als -"signed by an unknown certificate chain", `syspolicyd` macht daraufhin -einen `GK performScan` mit einer echten Netzwerkverbindung zu Apples -Gatekeeper-Servern — normalerweise sehr schnell, an diesem Tag aber -hängend/langsam. Jeder Neu-Build ändert den Binary-Hash, jeder Hash -braucht (in der Theorie) einen neuen Check. - -Betrifft nur den CLI-Weg (`xcodebuild test`, wodurch die App ohne -Debugger startet). Xcodes eigener Cmd+R-Weg ist davon nicht betroffen -(Debugger-gestützter Start bekommt von Xcode eine Gatekeeper-Ausnahme). -**Deshalb:** bei hartnäckigen `xcodebuild test`-Hängern erst -`xcodebuild ... build-for-testing` probieren (kompiliert nur, startet die -App nicht, kein Gatekeeper-Trigger) um Compile-Fehler auszuschließen, -und den eigentlichen Testlauf/die manuelle Verifikation dem Nutzer über -Xcode überlassen statt CLI-Versuche zu wiederholen. +`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) - **SSH-Hostkey-TOFU fehlt** — `SSHTransport` nutzt `.acceptAnything()`, - kein Trust-on-first-use wie bei REST. Kommentiert im Code als M7-Punkt. -- **CLI-Parser (`RouterOSCliParser`) ist jetzt gegen ein reales Gerät - verifiziert** (Verbinden-Tab zeigte korrekte Modell/Version/Interfaces), - aber nur gegen dieses eine Testgerät/diese RouterOS-Version. -- **Kein garantiertes Auto-Rollback** bei Verbindungsabbruch während - "Jetzt anwenden" — nur Backup-vorher + Bestätigungspflicht. Steht auch - im UI-Text auf dem Übersichtsschritt. -- **VLAN ohne Port-Zuweisung** (siehe oben, Architekturprinzip) — bewusste - Scope-Entscheidung, nicht mehr technisch erzwungen. -- **REST-Pfad ungetestet für Schreibvorgänge** — beim Testgerät ist - `www-ssl` (Port 443) aus, daher lief jeder bisherige Schreibtest über - SSH. Der REST-`apply()`-Pfad (`POST`/`PATCH` mit JSON-Body, - `findItemID` für `.set`) ist nur gegen Mocks getestet, nicht gegen ein - echtes Gerät mit aktiver REST-API. -- **WLAN-`.set`-Pfad (M5) ungetestet gegen echte Hardware** — der - "kein WLAN"-Zweig ist am echten Testgerät bestätigt (kein WLAN-Chip, - korrekt erkannt), aber Sicherheitsprofil-Anlage + SSID/Passwort-`.set` - auf einem echten `/interface wireless`-Interface noch nie gegen ein - Gerät mit WLAN-Chip gelaufen. Vor Vertrauen in diesen Pfad unbedingt - an einem Gerät mit Legacy-WLAN testen. + kein Trust-on-first-use wie bei REST. M7-Punkt, im Code kommentiert. +- **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. +- **Alte, fehlerhafte "lo"-Regeln stehen eventuell noch auf dem + hEX-Testgerät** — Aufräum-Befehle wurden dem Nutzer gegeben + (`/ip firewall nat remove [find out-interface=lo]`, + `/ip firewall filter remove [find in-interface=lo]`), Ausführung nicht + bestätigt. - **Neuer RouterOS-WiFi-Treiber (`/interface wifi`, wifiwave2/802.11ax) nicht unterstützt** — wird erkannt und im UI erklärt, aber nicht - konfiguriert. Eigener Umbau nötig (andere Menüstruktur), falls - gebraucht. + 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). ## Stand der Milestones -- ✅ M1: Projektgerüst, Connect-Schritt (REST/SSH-Autodetect, Keychain, Zertifikats-TOFU) -- ✅ M2: Backup-Service, Sicherungen-Tab -- ✅ M3: WAN + LAN/DHCP-Wizard, Anwenden-Logik mit Vorab-Backup -- ✅ M4: VLAN-Schritt (separates virtuelles Netz, kein Port-Tagging) -- ✅ M5: WLAN-Schritt (SSID/Passwort, echte `.set`-Operation im Befehlsmodell, - nur bei erkanntem Legacy-Wireless; neuer WiFi-Treiber erkannt aber nicht - unterstützt) — "kein WLAN"-Zweig gegen echtes Gerät bestätigt, der - eigentliche SSID/Passwort-`.set`-Pfad **weiterhin ungetestet** (kein - WLAN-Chip am Testgerät) -- ✅ M6: Firewall-Schritt (opt-in, Standard aus; NAT/Masquerade + sicherer - Filter-Standard mit `place-before`-Sortierung; zeigt vorhandene - Regelanzahl vor dem Anwenden) — **gebaut, noch nicht gegen echte - Hardware angewendet**, Nutzer verifiziert selbst über Xcode -- ⬜ M7: Härtung — SSH-Hostkey-TOFU, Fehlerzustände, Politur, ggf. REST-Schreibpfad - gegen echtes Gerät mit aktivem `www-ssl` verifizieren +- ✅ 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, Fehlerzustände, Politur, REST- + Schreibpfad gegen ein Gerät mit aktivem `www-ssl` verifizieren. ## Nächste Schritte -M6 (Firewall) am Testgerät (Ersatzgerät, nicht Produktiv-Router) -anwenden und danach die Regel-Reihenfolge prüfen (`/ip firewall filter -print`, `/ip firewall nat print`) — das ist der bisher riskanteste -Schritt und noch nie live gelaufen. Danach M5 (WLAN) nachholen, sobald -ein Gerät mit echtem WLAN-Chip verfügbar ist. Dann M7. +1. Auf dem hEX-Testgerät die alten "lo"-Firewallregeln aufräumen (siehe + oben) und den Firewall-Schritt mit jetzt korrekt erkanntem WAN-Port + erneut anwenden + Ergebnis per `/ip firewall filter print` / + `/ip firewall nat print` kontrollieren. +2. WLAN (M5) an einem Gerät mit echtem WLAN-Chip nachholen. +3. M7-Härtung, insbesondere SSH-Hostkey-TOFU und ein REST-Schreibtest an + einem Gerät mit aktivem `www-ssl`. Kein Gitea-Remote vorhanden — falls der Nutzer später eine Gitea-Instanz -aufsetzt (z.B. selbst gehostet auf der vorhandenen OMV-NAS, wo bereits -andere Docker-Dienste laufen, siehe `Aperto/`-Projekt), Remote hinzufügen -und pushen; bis dahin bleibt es bei lokalem Git. +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.