Handoff + Chatlog auf aktuellen Stand gebracht (Session-Ende)
HANDOFF.md komplett überarbeitet: M5/M6-Live-Test-Ergebnisse, den kritischen Interface-Parser-Bug (Bug 6) inkl. Ursache und Fix, neue Zusatzfeatures (Tooltips, wählbarer Backup-Ordner, Schnell-Backup im Verbinden-Tab, Werkseinstellungen-Reset, App-Icon), aktualisierte Einschränkungen (Firewall-Regeln mit korrektem WAN-Port noch nicht erneut kontrolliert, alte "lo"-Regeln evtl. noch auf dem Testgerät) und konkrete nächste Schritte. CHATLOG.md um die komplette Fortsetzung ergänzt: M5/M6-Bau, Live-Test mit dem hEX-Gerät, das gemeinsame Debugging des Interface-Bugs (PTY-Verdacht widerlegt durch Nutzer-Test, echte Ursache im Parser gefunden), sowie alle Nutzer-Wünsche danach (Tooltips, Backup-Pfad, Schnell-Backup, Werksreset, macOS-Deploy, App-Icon). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HReLXMbmPvtQ23p1iWiJNW
This commit is contained in:
+89
-1
@@ -105,8 +105,96 @@ Fix → erneuter Test:
|
|||||||
(Citadel-Version war längst über einen gezielteren Weg gefunden), keine
|
(Citadel-Version war längst über einen gezielteren Weg gefunden), keine
|
||||||
weitere Aktion nötig.
|
weitere Aktion nötig.
|
||||||
- Nutzer: "speicher das chatlog ebenfalls mit" → dieses Dokument.
|
- 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
|
## Stand am Ende dieser Session
|
||||||
|
|
||||||
Siehe `HANDOFF.md` für den vollständigen technischen Stand, offene
|
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.
|
||||||
|
|||||||
+158
-133
@@ -1,21 +1,21 @@
|
|||||||
# RouterOS Assistant — Handoff
|
# RouterOS Assistant — Handoff
|
||||||
|
|
||||||
Stand: nach M6 (Firewall-Schritt). M1–M4 gegen ein echtes physisches
|
Stand: App-Icon fertig, alle Kern-Features (M1–M6) gebaut und mindestens
|
||||||
Mikrotik-Testgerät (RouterOS 7.24.2) verifiziert. M5: nur der
|
teilweise gegen zwei echte physische Mikrotik-Testgeräte verifiziert
|
||||||
"kein-WLAN-erkannt"-Zweig ist bestätigt (siehe Einschränkungen unten). M6
|
(ein generisches Gerät für M1–M4, ein hEX-Router für M5/M6 — dabei ein
|
||||||
ist gebaut, Build + Test-Compile grün, aber **noch nicht gegen echte
|
kritischer Parser-Bug gefunden und gefixt, siehe unten). Danach noch
|
||||||
Hardware angewendet** — der Nutzer verifiziert diesen Schritt direkt in
|
mehrere Nutzer-Wünsche umgesetzt: Tooltips, wählbarer Backup-Ordner,
|
||||||
Xcode (Cmd+R), da `xcodebuild test` von der Kommandozeile aktuell an
|
Schnell-Backup im Verbinden-Tab, "Werkseinstellungen wiederherstellen",
|
||||||
einem macOS-Gatekeeper-Netzwerk-Check hängt (siehe eigener Abschnitt
|
eigenes App-Icon. Release-Build liegt unter
|
||||||
unten, kein Code-Bug).
|
`/Applications/RouterOS Assistant.app`.
|
||||||
|
|
||||||
## Ziel
|
## Ziel
|
||||||
|
|
||||||
Native macOS-App (SwiftUI), die Laien per geführtem Interview-Wizard durch
|
Native macOS-App (SwiftUI), die Laien per geführtem Interview-Wizard durch
|
||||||
die häufigsten Mikrotik-RouterOS-Konfigurationen führt: Internet-Anschluss,
|
die häufigsten Mikrotik-RouterOS-Konfigurationen führt: Internet-Anschluss,
|
||||||
Heimnetzwerk/DHCP, zusätzliche VLAN-Netzwerke, WLAN, (geplant: Firewall).
|
Heimnetzwerk/DHCP, zusätzliche VLAN-Netzwerke, WLAN, Firewall-Grundschutz,
|
||||||
Vollständiger Plan/Kontext: `~/.claude/plans/purrfect-puzzling-bunny.md`
|
plus Backup/Wiederherstellung. Vollständiger ursprünglicher Plan/Kontext:
|
||||||
(ursprüngliche Architekturentscheidung — seither an mehreren Stellen
|
`~/.claude/plans/purrfect-puzzling-bunny.md` (seither an vielen Stellen
|
||||||
weiterentwickelt, siehe unten).
|
weiterentwickelt, siehe unten).
|
||||||
|
|
||||||
## Setup / Bauen
|
## Setup / Bauen
|
||||||
@@ -25,19 +25,30 @@ cd RouterOS
|
|||||||
xcodegen generate # erzeugt RouterOSAssistant.xcodeproj (nicht in git)
|
xcodegen generate # erzeugt RouterOSAssistant.xcodeproj (nicht in git)
|
||||||
open RouterOSAssistant.xcodeproj # oder direkt per xcodebuild
|
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 \
|
xcodebuild -project RouterOSAssistant.xcodeproj -scheme RouterOSAssistant \
|
||||||
-destination 'platform=macOS' build
|
-destination 'platform=macOS' build
|
||||||
|
|
||||||
|
# Nur kompilieren, nicht ausführen (umgeht den Gatekeeper-Hang, siehe unten)
|
||||||
xcodebuild -project RouterOSAssistant.xcodeproj -scheme RouterOSAssistant \
|
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 "<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 (`brew install xcodegen`). Projekt-
|
`xcodegen` ist via Homebrew installiert. Projekt-Struktur/Targets/Package-
|
||||||
Struktur/Targets/Package-Abhängigkeiten stehen in `project.yml`, das ist die
|
Abhängigkeiten stehen in `project.yml`, das ist die Quelle der Wahrheit —
|
||||||
Quelle der Wahrheit — nicht das generierte `.xcodeproj` von Hand editieren.
|
nicht das generierte `.xcodeproj` von Hand editieren.
|
||||||
|
|
||||||
Git: lokales Repo, kein Remote (Nutzer hat aktuell keine Gitea-Instanz).
|
Git: lokales Repo, kein Remote (Nutzer hat aktuell keine Gitea-Instanz).
|
||||||
`.xcodeproj`, `DerivedData`, `.build` sowie die vorhandenen Firmware-Dateien
|
`.xcodeproj`, `DerivedData`, `.build`, Firmware-Dateien (`*.npk`, `*.cpgz`)
|
||||||
(`*.npk`, `*.cpgz`) sind gitignored.
|
und Router-Backups (`*.rsc`, `/Backups/`) sind gitignored.
|
||||||
|
|
||||||
## Architektur
|
## Architektur
|
||||||
|
|
||||||
@@ -51,154 +62,168 @@ RouterOSAssistant/
|
|||||||
DhcpServerCommandBuilder.swift — geteilte "Adresse+Pool+Server+Netzwerk"-Logik (LAN + VLAN)
|
DhcpServerCommandBuilder.swift — geteilte "Adresse+Pool+Server+Netzwerk"-Logik (LAN + VLAN)
|
||||||
RouterOSModels.swift — Credentials, DeviceInfo, Interface, RouterOSError
|
RouterOSModels.swift — Credentials, DeviceInfo, Interface, RouterOSError
|
||||||
Networking/
|
Networking/
|
||||||
RouterOSTransport.swift — Protocol: connect/fetchDeviceInfo/fetchInterfaces/apply/disconnect
|
RouterOSTransport.swift — Protocol: connect/fetchDeviceInfo/fetchInterfaces/fetchFirewallRuleCounts/apply/disconnect
|
||||||
RestTransport.swift — REST-API (RouterOS ≥7.1), Zertifikats-TOFU
|
RestTransport.swift — REST-API (RouterOS ≥7.1), Zertifikats-TOFU, .set via GET+PATCH (findItemID)
|
||||||
SSHTransport.swift — SSH-Fallback via Citadel, CLI-Text-Parsing
|
SSHTransport.swift — SSH-Fallback via Citadel, CLI-Text-Parsing, resetToFactoryDefaults()
|
||||||
RouterOSCliParser.swift — parst `/system resource print` und `/interface print terse`
|
RouterOSCliParser.swift — parst `/system resource print` und `/interface print terse`
|
||||||
CertificateTrustStore.swift / CertificateFingerprint.swift — TOFU nur für REST bisher
|
CertificateTrustStore.swift / CertificateFingerprint.swift — TOFU nur für REST bisher
|
||||||
Services/
|
Services/
|
||||||
ConnectionService.swift — zentraler App-State: REST-zuerst-SSH-Fallback, hält credentials/interfaces/deviceInfo
|
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
|
KeychainService.swift — Passwort-Speicherung
|
||||||
Features/
|
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
|
Wizard/Steps/Setup/ — Einrichten-Tab: Wan → Lan → Vlan → Wifi → Firewall → Review/Apply
|
||||||
Backup/ — Sicherungen-Tab
|
Backup/ — Sicherungen-Tab (Ordner wählen, Gefahrenzone: Werkseinstellungen wiederherstellen)
|
||||||
RouterOSAssistantTests/ — reine Unit-Tests (Command-Builder, CLI-Parser, Fallback-Logik via Mock-Transport)
|
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`
|
**Architekturprinzip `RouterOSCommand` (.add/.set):** `.add` legt neue
|
||||||
kennt zwei Operationen: `.add` (neuer Eintrag — CLI `... add key=value`,
|
Einträge an (CLI `... add key=value`, REST `POST`). `.set` ändert
|
||||||
REST `POST`) und `.set` (bestehenden Eintrag ändern, per `matchField`/
|
bestehende Einträge, per `matchField`/`matchValue` gefunden — CLI löst das
|
||||||
`matchValue` gefunden — CLI `... set [find field=value] key=value` löst
|
inline über `set [find field=value] ...`, REST muss dafür erst per GET das
|
||||||
das inline, REST muss dafür erst per GET das Item suchen, seine
|
Item suchen, seine RouterOS-interne `.id` auslesen, dann
|
||||||
RouterOS-interne `.id` auslesen, dann `PATCH restPath/<id>` schicken,
|
`PATCH restPath/<id>` schicken (`RestTransport.findItemID`). Eingeführt für
|
||||||
siehe `RestTransport.findItemID`). Eingeführt für M5 (WLAN), weil
|
WLAN (M5), weil Wireless-Interfaces schon vor jeder Konfiguration
|
||||||
Wireless-Interfaces schon vor jeder Konfiguration existieren — kein
|
existieren. VLAN (M4) hat trotzdem weiterhin keine Port-Zuweisung
|
||||||
"add" möglich. VLAN (M4) hat trotzdem weiterhin keine Port-Zuweisung
|
(Access/Trunk) — technisch jetzt machbar, aber bewusste Scope-Entscheidung,
|
||||||
(Access/Trunk): das bräuchte zusätzlich RouterOS Bridge-VLAN-Filtering,
|
nicht nachgezogen.
|
||||||
technisch jetzt machbar, aber bewusst nicht rückwirkend nachgezogen
|
|
||||||
(Scope-Entscheidung, nicht mehr Architektur-Zwang).
|
**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
|
**ConnectionService ist der einzige geteilte State.** Alle drei Tabs
|
||||||
bekommen dieselbe Instanz von der App-Ebene injiziert (kein
|
bekommen dieselbe Instanz von der App-Ebene injiziert (kein
|
||||||
`@EnvironmentObject`, explizite Übergabe im Init). Achtung SwiftUI-Falle
|
`@EnvironmentObject`, explizite Übergabe im Init). SwiftUI-Falle: jede
|
||||||
(siehe unten): jede View, die `connectionService`-Felder liest, muss ihn
|
View, die `connectionService`-Felder liest, muss ihn selbst als
|
||||||
selbst als `@ObservedObject` halten — nicht nur transitiv über ein anderes
|
`@ObservedObject` halten — nicht nur transitiv über ein anderes ViewModel
|
||||||
ViewModel erreichen.
|
erreichen (siehe Bug 1 unten).
|
||||||
|
|
||||||
## Was während der Live-Tests kaputt war und wie es gefixt wurde
|
## Gefundene Bugs (chronologisch, alle gegen echte Geräte)
|
||||||
|
|
||||||
Reihenfolge der Bugs, alle gegen echtes Testgerät gefunden, jeweils als
|
|
||||||
eigener Commit (`git log` zeigt Details):
|
|
||||||
|
|
||||||
1. **ConnectView zeigte nie Verbindungsstatus** — `connectionService` war
|
1. **ConnectView zeigte nie Verbindungsstatus** — `connectionService` war
|
||||||
nur über `viewModel.connectionService` erreichbar (zwei Ebenen tief
|
nur über `viewModel.connectionService` erreichbar (zwei Ebenen tief
|
||||||
verschachteltes ObservableObject). SwiftUI abonniert automatisch nur
|
verschachteltes ObservableObject), SwiftUI abonniert das nicht
|
||||||
Objekte, die eine View direkt per `@StateObject`/`@ObservedObject` hält.
|
automatisch. Fix: `ConnectView` hält `connectionService` zusätzlich
|
||||||
Fix: `ConnectView` hält `connectionService` jetzt zusätzlich selbst.
|
selbst als `@ObservedObject`.
|
||||||
2. **NIOSSHError.localizedDescription ist nutzlos** — bridged auf
|
2. **`NIOSSHError.localizedDescription` nutzlos** — bridged auf
|
||||||
generisches NSError (`"error 1"`), echte Diagnose steckt in
|
generisches NSError (`"error 1"`). Fix: `String(describing:)`.
|
||||||
`CustomStringConvertible`. Fix: `String(describing:)` statt
|
3. **SSH scheiterte mit `keyExchangeNegotiationFailure`** — RouterOS bietet
|
||||||
`.localizedDescription`.
|
nur Legacy-Algorithmen an, Citadel-Default enthält sie nicht. Fix:
|
||||||
3. **SSH-Verbindung scheiterte mit `keyExchangeNegotiationFailure`** —
|
`algorithms: .all`.
|
||||||
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(...)`.
|
|
||||||
4. **`Citadel.SSHClient.CommandFailed` zeigte nur den Exit-Code** —
|
4. **`Citadel.SSHClient.CommandFailed` zeigte nur den Exit-Code** —
|
||||||
Citadels `executeCommand()` verwirft die gesammelte Ausgabe, sobald der
|
`executeCommand()` verwirft die gesammelte Ausgabe bei Exit-Code ≠ 0.
|
||||||
Exit-Code ≠ 0 ist — genau der Text, den RouterOS bei einem Fehler
|
Fix: eigene Sammlung über `executeCommandStream()`.
|
||||||
zurückgibt. Fix: eigene Sammlung über `executeCommandStream()` in
|
5. **Picker-Warnungen durch Platzhalter-Interfacenamen** — `prepareDefaults()`
|
||||||
`SSHTransport.run(_:)`, damit die Ausgabe bis zum Fehlschlag erhalten
|
korrigiert jetzt WAN und LAN, läuft schon im `init`.
|
||||||
bleibt.
|
6. **Kritisch: CLI-Parser verlor 6 von 7 Interfaces auf einem hEX-Router** —
|
||||||
5. **Picker-Warnungen** ("selection is invalid and does not have an
|
`parseInterfaces`/`parseDeviceInfo` splitteten nur auf `"\n"`; die
|
||||||
associated tag") — `wanConfig`/`lanConfig` starteten mit festen
|
SSH-Ausgabe dieses Geräts trennte Zeilen anders, sodass alle
|
||||||
Platzhaltern (`"ether1"`/`"bridge"`), die nicht zu den echten
|
Interface-Zeilen zu einer verschmolzen — beim key=value-Parsen
|
||||||
Interface-Namen des Geräts passen mussten. Fix: `prepareDefaults()`
|
überschrieb jedes Feld den vorherigen Wert, übrig blieb nur das letzte
|
||||||
korrigiert jetzt beide anhand eines "ist der Wert überhaupt in der
|
Interface im Text (`lo`, Loopback) mit vermischten Werten. Folge: **jede
|
||||||
Liste"-Checks, läuft schon im `init`.
|
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
|
**Lehren:** Citadel/NIOSSH-Fehler immer mit `String(describing:)` loggen,
|
||||||
`String(describing:)` loggen, nie `.localizedDescription` vertrauen. Jede
|
nie `.localizedDescription`. Jede View, die ein ObservableObject aus einem
|
||||||
neue View, die ein ObservableObject aus einem ViewModel liest, muss es
|
ViewModel liest, muss es selbst separat als `@ObservedObject` halten.
|
||||||
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
|
## `xcodebuild test` hängt — Gatekeeper, kein Code-Bug
|
||||||
|
|
||||||
Bei M6 hing `xcodebuild ... test` dreimal reproduzierbar exakt beim
|
`xcodebuild ... test` kann exakt beim App-Start für ~5 Minuten hängen,
|
||||||
App-Start für ~5 Minuten, dann `"The test runner hung before establishing
|
dann `"The test runner hung before establishing connection."`. Ursache
|
||||||
connection."`. Ursache laut `log show` (Filter auf `RouterOSAssistant`/
|
(per `log show`, Filter auf `syspolicyd`/`amfid`): ad-hoc-signierte
|
||||||
`syspolicyd`/`amfid`): `amfid` markiert das ad-hoc-signierte Binary als
|
Binaries lösen bei `syspolicyd` einen Live-Netzwerk-Check
|
||||||
"signed by an unknown certificate chain", `syspolicyd` macht daraufhin
|
(`GK performScan`) gegen Apples Gatekeeper-Server aus; jeder Neu-Build
|
||||||
einen `GK performScan` mit einer echten Netzwerkverbindung zu Apples
|
ändert den Binary-Hash und kann einen neuen, manchmal langsamen Check
|
||||||
Gatekeeper-Servern — normalerweise sehr schnell, an diesem Tag aber
|
auslösen. Betrifft nur den CLI-Weg — Xcodes Cmd+R-Weg bekommt vom
|
||||||
hängend/langsam. Jeder Neu-Build ändert den Binary-Hash, jeder Hash
|
Debugger-Start eine Gatekeeper-Ausnahme. **Deshalb:** bei Hängern erst
|
||||||
braucht (in der Theorie) einen neuen Check.
|
`xcodebuild ... build-for-testing` (kompiliert nur, kein App-Start, kein
|
||||||
|
Gatekeeper-Trigger) für den Compile-Check nutzen, echte Testläufe/manuelle
|
||||||
Betrifft nur den CLI-Weg (`xcodebuild test`, wodurch die App ohne
|
Verifikation dem Nutzer über Xcode überlassen statt CLI-Versuche zu
|
||||||
Debugger startet). Xcodes eigener Cmd+R-Weg ist davon nicht betroffen
|
wiederholen.
|
||||||
(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.
|
|
||||||
|
|
||||||
## Bekannte Einschränkungen (bewusst, nicht vergessen)
|
## Bekannte Einschränkungen (bewusst, nicht vergessen)
|
||||||
|
|
||||||
- **SSH-Hostkey-TOFU fehlt** — `SSHTransport` nutzt `.acceptAnything()`,
|
- **SSH-Hostkey-TOFU fehlt** — `SSHTransport` nutzt `.acceptAnything()`,
|
||||||
kein Trust-on-first-use wie bei REST. Kommentiert im Code als M7-Punkt.
|
kein Trust-on-first-use wie bei REST. M7-Punkt, im Code kommentiert.
|
||||||
- **CLI-Parser (`RouterOSCliParser`) ist jetzt gegen ein reales Gerät
|
- **REST-Pfad ungetestet für Schreibvorgänge** — auf beiden bisherigen
|
||||||
verifiziert** (Verbinden-Tab zeigte korrekte Modell/Version/Interfaces),
|
Testgeräten war `www-ssl` (Port 443) aus, jeder Schreibtest lief über
|
||||||
aber nur gegen dieses eine Testgerät/diese RouterOS-Version.
|
SSH. Der REST-`apply()`-Pfad (`POST`/`PATCH`, `findItemID` für `.set`)
|
||||||
- **Kein garantiertes Auto-Rollback** bei Verbindungsabbruch während
|
ist nur gegen Mocks getestet.
|
||||||
"Jetzt anwenden" — nur Backup-vorher + Bestätigungspflicht. Steht auch
|
- **WLAN-`.set`-Pfad (M5) weiterhin ungetestet gegen echte Hardware** —
|
||||||
im UI-Text auf dem Übersichtsschritt.
|
nur der "kein WLAN"-Zweig ist bestätigt (zwei Testgeräte, beide ohne
|
||||||
- **VLAN ohne Port-Zuweisung** (siehe oben, Architekturprinzip) — bewusste
|
WLAN-Chip). Sicherheitsprofil-Anlage + SSID/Passwort-`.set` auf einem
|
||||||
Scope-Entscheidung, nicht mehr technisch erzwungen.
|
echten `/interface wireless`-Interface noch nie live gelaufen.
|
||||||
- **REST-Pfad ungetestet für Schreibvorgänge** — beim Testgerät ist
|
- **Firewall-Regeln mit korrektem WAN-Interface noch nicht erneut
|
||||||
`www-ssl` (Port 443) aus, daher lief jeder bisherige Schreibtest über
|
bestätigt** — der erste Live-Test (M6) lief technisch durch, traf aber
|
||||||
SSH. Der REST-`apply()`-Pfad (`POST`/`PATCH` mit JSON-Body,
|
wegen Bug 6 oben `lo` statt des echten WAN-Ports. Nach dem Parser-Fix
|
||||||
`findItemID` für `.set`) ist nur gegen Mocks getestet, nicht gegen ein
|
wurde nur die Interface-**Auswahl** erneut bestätigt ("alle Interfaces
|
||||||
echtes Gerät mit aktiver REST-API.
|
auswählbar"), nicht aber ein erneuter Firewall-Apply mit korrektem
|
||||||
- **WLAN-`.set`-Pfad (M5) ungetestet gegen echte Hardware** — der
|
Interface + Kontrolle der resultierenden Regeln.
|
||||||
"kein WLAN"-Zweig ist am echten Testgerät bestätigt (kein WLAN-Chip,
|
- **Alte, fehlerhafte "lo"-Regeln stehen eventuell noch auf dem
|
||||||
korrekt erkannt), aber Sicherheitsprofil-Anlage + SSID/Passwort-`.set`
|
hEX-Testgerät** — Aufräum-Befehle wurden dem Nutzer gegeben
|
||||||
auf einem echten `/interface wireless`-Interface noch nie gegen ein
|
(`/ip firewall nat remove [find out-interface=lo]`,
|
||||||
Gerät mit WLAN-Chip gelaufen. Vor Vertrauen in diesen Pfad unbedingt
|
`/ip firewall filter remove [find in-interface=lo]`), Ausführung nicht
|
||||||
an einem Gerät mit Legacy-WLAN testen.
|
bestätigt.
|
||||||
- **Neuer RouterOS-WiFi-Treiber (`/interface wifi`, wifiwave2/802.11ax)
|
- **Neuer RouterOS-WiFi-Treiber (`/interface wifi`, wifiwave2/802.11ax)
|
||||||
nicht unterstützt** — wird erkannt und im UI erklärt, aber nicht
|
nicht unterstützt** — wird erkannt und im UI erklärt, aber nicht
|
||||||
konfiguriert. Eigener Umbau nötig (andere Menüstruktur), falls
|
konfiguriert.
|
||||||
gebraucht.
|
- **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
|
## Stand der Milestones
|
||||||
|
|
||||||
- ✅ M1: Projektgerüst, Connect-Schritt (REST/SSH-Autodetect, Keychain, Zertifikats-TOFU)
|
- ✅ M1–M4: Projektgerüst, Connect, Backup, WAN/LAN/DHCP, VLAN — gegen
|
||||||
- ✅ M2: Backup-Service, Sicherungen-Tab
|
echtes Testgerät verifiziert.
|
||||||
- ✅ M3: WAN + LAN/DHCP-Wizard, Anwenden-Logik mit Vorab-Backup
|
- ✅ M5: WLAN-Schritt — nur "kein WLAN"-Zweig verifiziert, `.set`-Pfad
|
||||||
- ✅ M4: VLAN-Schritt (separates virtuelles Netz, kein Port-Tagging)
|
weiterhin ungetestet (kein WLAN-Chip auf beiden Testgeräten bisher).
|
||||||
- ✅ M5: WLAN-Schritt (SSID/Passwort, echte `.set`-Operation im Befehlsmodell,
|
- ✅ M6: Firewall-Schritt — live angewendet, dabei Bug 6 gefunden+gefixt;
|
||||||
nur bei erkanntem Legacy-Wireless; neuer WiFi-Treiber erkannt aber nicht
|
Interface-Auswahl danach erneut bestätigt, Regel-Ergebnis mit korrektem
|
||||||
unterstützt) — "kein WLAN"-Zweig gegen echtes Gerät bestätigt, der
|
WAN-Port aber noch nicht erneut kontrolliert.
|
||||||
eigentliche SSID/Passwort-`.set`-Pfad **weiterhin ungetestet** (kein
|
- ✅ Zusatzfeatures nach M6: Tooltips auf allen Konfigurationsfeldern,
|
||||||
WLAN-Chip am Testgerät)
|
wählbarer Backup-Ordner (Standard: `~/Library/Application Support/...`),
|
||||||
- ✅ M6: Firewall-Schritt (opt-in, Standard aus; NAT/Masquerade + sicherer
|
Schnell-Backup-Button im Verbinden-Tab, "Werkseinstellungen
|
||||||
Filter-Standard mit `place-before`-Sortierung; zeigt vorhandene
|
wiederherstellen" (Gefahrenzone im Sicherungen-Tab,
|
||||||
Regelanzahl vor dem Anwenden) — **gebaut, noch nicht gegen echte
|
`/system reset-configuration no-defaults=no`), eigenes App-Icon
|
||||||
Hardware angewendet**, Nutzer verifiziert selbst über Xcode
|
("Signal Router"-Motiv).
|
||||||
- ⬜ M7: Härtung — SSH-Hostkey-TOFU, Fehlerzustände, Politur, ggf. REST-Schreibpfad
|
- ⬜ M7: Härtung — SSH-Hostkey-TOFU, Fehlerzustände, Politur, REST-
|
||||||
gegen echtes Gerät mit aktivem `www-ssl` verifizieren
|
Schreibpfad gegen ein Gerät mit aktivem `www-ssl` verifizieren.
|
||||||
|
|
||||||
## Nächste Schritte
|
## Nächste Schritte
|
||||||
|
|
||||||
M6 (Firewall) am Testgerät (Ersatzgerät, nicht Produktiv-Router)
|
1. Auf dem hEX-Testgerät die alten "lo"-Firewallregeln aufräumen (siehe
|
||||||
anwenden und danach die Regel-Reihenfolge prüfen (`/ip firewall filter
|
oben) und den Firewall-Schritt mit jetzt korrekt erkanntem WAN-Port
|
||||||
print`, `/ip firewall nat print`) — das ist der bisher riskanteste
|
erneut anwenden + Ergebnis per `/ip firewall filter print` /
|
||||||
Schritt und noch nie live gelaufen. Danach M5 (WLAN) nachholen, sobald
|
`/ip firewall nat print` kontrollieren.
|
||||||
ein Gerät mit echtem WLAN-Chip verfügbar ist. Dann M7.
|
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
|
Kein Gitea-Remote vorhanden — falls der Nutzer später eine Gitea-Instanz
|
||||||
aufsetzt (z.B. selbst gehostet auf der vorhandenen OMV-NAS, wo bereits
|
aufsetzt (z.B. selbst gehostet auf der vorhandenen OMV-NAS, siehe
|
||||||
andere Docker-Dienste laufen, siehe `Aperto/`-Projekt), Remote hinzufügen
|
`Aperto/`-Projekt), Remote hinzufügen und pushen; bis dahin lokales Git.
|
||||||
und pushen; bis dahin bleibt es bei lokalem 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.
|
||||||
|
|||||||
Reference in New Issue
Block a user