Letzter offener Punkt aus HANDOFF.md: gespeicherte .rsc-Backups lassen
sich jetzt wieder einspielen. Weg: Backup-Datei per SFTP auf den Router
hochladen (Citadel, die bereits eingebundene SSH-Bibliothek, hat einen
SFTP-Client), dann RouterOS' offiziell dokumentierter Restore-Weg in
einem Rutsch: /system reset-configuration no-defaults=yes
run-after-reset=<datei> (kompletter Wipe + sofortiges Wiederanwenden,
sicherer als ein Re-Import auf eine bestehende, andere Config).
Vor dem Bestätigungsdialog wird das Routermodell abgeglichen
(/system routerboard prints "model"-Feld gegen die "# model = ..."-
Kopfzeile der Sicherung) und bei Mismatch komplett blockiert, um ein
Brick-Risiko durch falsches Modell zu vermeiden - der Dialog selbst warnt
zusaetzlich prominent davor.
Ein ernster Bug live gefunden und gefixt: der erste echte Restore-Test
sperrte den Router komplett aus (RouterOS exportiert nie Passwoerter,
no-defaults=yes loescht zusaetzlich den Werks-Admin-Account), nur per
Hardware-Reset behebbar. Fix: das aktuell verwendete App-Login wird jetzt
vorne ins Restore-Skript eingefuegt, noch vor dem eigentlichen
Sicherungsinhalt, da RouterOS den Import beim ersten Fehler irgendwo im
Skript komplett abbricht. Zwei Tests fuer die Escaping-Logik ergaenzt.
Nebenbei: BackupListView mit den neuen Restore-Dialogen liess sich nicht
mehr kompilieren (SwiftUI-Typpruefung timeoutete bei der langen
Modifier-Kette) - Restore-Dialoge in eine eigene @ViewBuilder-Property
ausgelagert.
HANDOFF.md/CHATLOG.md aktualisiert: M13, Bug 18+19, "Backup-
Wiederherstellung fehlt" aus den offenen Punkten entfernt, Hinweis auf
den zweiten (ungewollten) Werksreset waehrend der Session.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CTgRxJTzaQwaRkngbaE1GJ
RouterOS kennt kein make-dynamic als Umkehrung von make-static (per
Recherche-Agent gegen die offizielle DHCP-Doku bestätigt: nur
check-status/make-static/send-reconfigure existieren) — der offizielle
Weg ist, die statische Lease zu entfernen; das Gerät bekommt beim
naechsten Verbindungsaufbau automatisch wieder eine dynamische Adresse,
moeglicherweise eine andere IP als zuvor.
Neuer Kontextmenü-Eintrag bei fest zugewiesenen Geräten im Geräte-Tab,
mit Bestätigungsdialog (erklärt den Ablauf) und Nachkontrolle, dass der
Lease-Eintrag wirklich entfernt wurde, bevor Erfolg gemeldet wird -
gleiche Vorsicht wie beim bestehenden "Feste IP zuweisen". Vom Nutzer
live bestätigt (fest zuweisen -> entfernen -> Kabel/WLAN neu verbinden
-> wieder dynamisch), kein neuer Bug diesmal.
HANDOFF.md/CHATLOG.md aktualisiert: "Zurück auf dynamisch" aus den
offenen Punkten entfernt, M12-Beschreibung ergänzt.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CTgRxJTzaQwaRkngbaE1GJ
Neuer Tab: eine Tabelle pro physischem Ethernet/WLAN-Port mit den dort
gefundenen Geräten (Name, IP, MAC, Fest/Dynamisch/Kein-DHCP), gebaut aus
DHCP-Leases + ARP + Bridge-Host-Tabelle. Rechtsklick auf ein dynamisches
Gerät -> "Feste IP zuweisen" (RouterOS' "Make Static", per
/ip dhcp-server lease make-static), mit Bestätigungsdialog und
Session-Backup vor dem ersten Schreibvorgang (geteilter Mechanismus mit
dem Experte-Tab).
Vier reale Bugs live gefunden und gefixt (siehe HANDOFF.md Bug 14-17):
- "print terse" gibt das "dynamic"-Feld von /ip dhcp-server lease nie
aus, in keinem Zustand -> Status kommt jetzt über RouterOS' find/get
gegen die interne Eigenschaft, nicht aus gelesenen Feldern.
- fetchMenuItems' .id-Positionsüberlagerung ordnete für dieses Menü die
falsche .id der falschen Zeile zu -> Erkennung und make-static-Ziel
laufen jetzt über die MAC-Adresse statt .id.
- Ein SwiftUI-.confirmationDialog löschte sein eigenes Ziel-Objekt vor
der Ausführung der bestätigten Aktion (Setter feuert bei jedem
Knopfdruck, nicht nur Abbrechen) -> Dialog-Sichtbarkeit und
Nutzlast entkoppelt, wie in BackupListView.
- Die eigene Verifikations-Abfrage (get [find ...] feld als ein
kombinierter Befehl) war selbst eine nie verifizierte Annahme und
lieferte falsche Negative -> ersetzt durch :foreach aus zwei einzeln
bestätigten Bausteinen (find, get <id> feld).
RouterOSCommand bekommt einen neuen .action-Operationstyp für
RouterOS-"Menü-spezifische Befehle" jenseits von add/set/remove (aktuell
nur make-static). HANDOFF.md/CHATLOG.md mit allen vier Bugs, neuen
Milestones M11/M12 und offenen Punkten aktualisiert.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CTgRxJTzaQwaRkngbaE1GJ
Neuer Tab zeigt die komplette aktuelle Router-Konfiguration als grafisches
Diagramm: Interfaces, IP-Adressen, DHCP/Pools, Routen und Firewall/NAT in
Spalten, verbunden durch Linien, die RouterOS' eigene Referenzfelder
abbilden (VLAN->Basis-Interface, DHCP-Server->Pool, Firewall-Regel->
Interface/Adress-Liste usw.), nicht geraten. Rein lesend, kein apply().
Verbindungsarten sind farblich getrennt (8 Kategorien), Hover/Klick auf
eine Karte hebt ihre Linien hervor und blendet den Rest ab. Detail-Panel
zeigt Rohfelder + Verbindungen; Legende erklärt Spalten, Farben und listet
bewusst nicht gegraphte Bereiche (VPN, WLAN-Sicherheitsprofile, Queues,
System, Werkzeuge, Mangle/Raw), die weiterhin im Experte-Tab erreichbar
bleiben.
OverviewViewModel.buildGraph ist eine reine, nonisolated Funktion,
getestet in OverviewGraphTests (6 Fälle: IP/Interface, VLAN-Parent,
DHCP->Pool, DHCP-Netzwerk->passende IP-Adresse über RouterOS' eigenes
"network"-Feld, Route nur bei Interface-Gateway, Firewall->Interface/
Adress-Liste).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CTgRxJTzaQwaRkngbaE1GJ
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
CLI-Referenz für den hEX/RB750Gr3: gemeinsame LAN-Bridge vs. Port-pro-Netz,
Isolation über /interface list + in/out-interface-list statt einzelner
Paar-Regeln, Port aus bestehender Bridge herauslösen. Hintergrundmaterial
für kommende Arbeit an M8/Netzwerk-Isolation — noch nicht in der App
verarbeitet.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YYoWFMLHACvzzRC8u4iKF9
lanConfig wird zu lanConfigs: [LanDhcpConfig] (analog zum VLAN-Listen-
Muster) — mehrere physische Interfaces mit je eigenem DHCP-Server.
Neues isolated-Feld auf LanDhcpConfig/VlanEntry: FirewallConfig erzeugt
daraus paarweise Forward-Drop-Regeln zwischen jedem isolierten Netzwerk
und allen anderen konfigurierten Netzwerken (Pair-Dedup bei gegenseitiger
Isolation). Behebt nebenbei, dass VlanStepView bisher Isolation im
Hilfetext behauptete, ohne dass eine Regel das durchsetzte.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YYoWFMLHACvzzRC8u4iKF9
Kompletter Verlauf von Werksreset-Meldung über SSH-Hostkey-TOFU-Bau
(API-Verifikation im swift-nio-ssh-Quellcode, Fingerprint-Kreuzcheck
per ssh-keygen, Test-Isolationsbug gefunden+gefixt) bis zum fehlenden
Trennen-Button und der finalen Live-Bestätigung.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HReLXMbmPvtQ23p1iWiJNW
ConnectionService.disconnect() existierte schon (bisher nur intern
nach Werksreset genutzt), aber ohne UI-Zugang im Verbinden-Tab selbst
-- Nutzer musste die App neu starten, um eine Verbindung sauber zu
beenden. Jetzt als Button direkt neben dem "Verbunden"-Status.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HReLXMbmPvtQ23p1iWiJNW
M7 als teilweise erledigt markiert (implementiert, aber noch nicht
gegen echte Hardware getestet), alte lo-Regel-Einschränkung entfernt
(durch Werksreset des Testgeräts erledigt), nächste Schritte um den
neuen TOFU-Dialog beim ersten Verbinden ergänzt.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HReLXMbmPvtQ23p1iWiJNW
Größte offene Härtungslücke geschlossen: SSHTransport nutzte bisher
.acceptAnything() für Host-Key-Validierung, akzeptierte also jeden
Schlüssel ohne Prüfung -- ein Man-in-the-Middle im lokalen Netz wäre
unbemerkt geblieben. Jetzt Trust-on-first-use wie beim REST-Zertifikat:
- SSHHostKeyFingerprint: SHA256 über NIOSSHPublicKey.write(to:) (die
SSH-Wire-Format-Bytes des Schlüssels) -- exakt die Bytes, die auch
OpenSSH für seine SHA256:-Fingerabdrücke hasht. Per Unit-Test gegen
einen echten ssh-keygen-erzeugten Testschlüssel kreuzgeprüft
(SHA256:Hllxv6LLoHl2XTIXGGjUYJHbPFoH2F7iMrR74C5J95g), nicht geraten.
- SSHHostKeyTrustStore: UserDefaults-Persistenz pro Host, Pendant zu
CertificateTrustStore.
- SSHTransport conformt jetzt selbst zu NIOSSHClientServerAuthentication-
Delegate (wie RestTransport zu URLSessionDelegate) und übergibt sich
selbst als .custom(self) Host-Key-Validator.
- ConnectionService: neuer State .needsSSHHostKeyConfirmation, eigener
Bestätigungs-Retry-Pfad (trustCurrentSSHHostKeyAndRetry), analog zum
bestehenden Zertifikat-Flow.
- ConnectView: zweiter Bestätigungsdialog mit Warnhinweis, dass ein
geänderter Fingerabdruck bei zuvor schon verbundenen Routern auf ein
manipuliertes Netzwerk hindeuten könnte.
BackupService/FactoryResetService bekommen die TOFU-Prüfung automatisch
mit (SSHTransport-Default-Parameter, gleicher UserDefaults-Speicher),
ohne eigene Bestätigungs-UI -- in der Praxis unkritisch, da der
Verbinden-Tab das Vertrauen immer zuerst herstellt.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HReLXMbmPvtQ23p1iWiJNW
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
Eigenständiges Router-Symbol (Antennen + Gerätekörper mit Status-LEDs)
auf Türkis-Verlauf -- kein Nachbau von Mikrotiks eingetragenem Logo,
nur farblich an RouterOS angelehnt. Als SVG entworfen, per kleinem
AppKit/NSImage-Skript (kein Homebrew/librsvg nötig) in alle macOS-
Icongrößen von 16px bis 1024px gerendert.
project.yml: ASSETCATALOG_COMPILER_APPICON_NAME=AppIcon ergänzt, damit
Xcode das neue AppIcon.appiconset als App-Icon verwendet.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HReLXMbmPvtQ23p1iWiJNW
RouterOS bringt eine eigene Standardkonfiguration mit, wiederherstellbar
über /system reset-configuration no-defaults=no -- restauriert die vom
Hersteller ausgelieferte Konfiguration (nicht eine leere), RouterOS legt
dabei selbst zusätzlich ein Backup an (skip-backup=no, Standard).
FactoryResetService läuft wie BackupService immer über eine eigene
SSH-Verbindung (kein verifiziertes REST-Äquivalent, passt nicht ins
add/set-Modell von RouterOSCommand). Vor dem Zurücksetzen erstellt die
App zusätzlich selbst ein Backup (best-effort). Verbindung zum Router
bricht durch den Reboot erwartungsgemäß ab -- wird nicht als Fehler
behandelt, ConnectionService trennt sich danach selbst.
UI: rot markierte "Gefahrenzone" im Sicherungen-Tab, destruktiver
Bestätigungsdialog vor dem Ausführen, klarer Hinweistext was verloren
geht.
Außerdem .gitignore um /Backups/ und *.rsc ergänzt -- beim Testen des
wählbaren Backup-Ordners landete ein echter Router-Export im
Projektordner, gehört nicht ins Repo.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HReLXMbmPvtQ23p1iWiJNW
"Jetzt sichern"-Button erscheint sobald verbunden, noch bevor man in
den Einrichten-Tab wechselt -- Nutzer-Wunsch, um vor Änderungen ohne
Tab-Wechsel sichern zu können. Zeigt zusätzlich "Zuletzt gesichert: ..."
für den aktuell verbundenen Host, falls schon eine Sicherung existiert
(gefiltert auf den aktuellen Host, damit es bei mehreren Routern nicht
verwirrt). Nutzt dieselbe BackupViewModel/BackupService-Logik wie der
Sicherungen-Tab, eigene Instanz.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HReLXMbmPvtQ23p1iWiJNW
.help(...)-Tooltips auf jedem Eingabefeld/Picker/Toggle in Connect-
sowie allen Einrichten-Schritten (WAN/LAN/VLAN/WLAN/Firewall) --
kurze Erklärung was der Wert bedeutet und welche Auswirkung er hat,
passend zum Laien-Anspruch der App.
Sicherungen-Tab: Speicherort jetzt änderbar (Ordner wählen über
nativen macOS-Dialog, Zurücksetzen auf Standard). BackupService hält
den gewählten Pfad in UserDefaults (BackupService.customDirectoryURL),
fällt ohne Auswahl weiter auf ~/Library/Application Support/... zurück.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HReLXMbmPvtQ23p1iWiJNW
Kritischer Fund beim M6-Live-Test: RouterOSCliParser.parseInterfaces
splittete nur auf "\n", aber die SSH-Ausgabe eines echten hEX-Routers
trennt Zeilen anders -- alle Interface-Zeilen wurden zu EINER Zeile
zusammengefasst. Beim key=value-Parsen dieser einen Riesenzeile
überschrieb jedes Feld (name=, type=, ...) den vorherigen Wert, sodass
am Ende nur das letzte Interface im Text ("lo", Loopback) übrig blieb
-- mit den Feldwerten aller anderen Interfaces vermischt.
Folge: WAN-Schritt zeigte nur "lo" zur Auswahl, wodurch alle
WAN-Interface-Referenzen (NAT-Masquerade, ICMP-Regel, WAN-Block-Regel,
finale Anti-Spoofing-Regel) fälschlich auf "lo" statt den echten
WAN-Port zeigten. Auf diesem Testgerät blieb es folgenlos, weil RouterOS
schon eine vollständige eigene Standard-Firewall (defconf) mitbrachte,
die den echten Schutz weiterhin übernahm -- auf einem Gerät ohne
bestehende Firewall hätte das eine wirkungslose Firewall bedeutet, die
sich als aktiv ausgegeben hätte.
Fix: split(whereSeparator: \.isNewline) statt split(separator: "\n"),
robust gegen \n/\r/\r\n. Zusätzliches Sicherheitsnetz in SetupView:
Loopback-Interfaces werden aus allen WAN/LAN/VLAN-Auswahllisten
gefiltert, damit ein ähnlicher Parser-Fehler künftig nicht erneut zu
einer sinnlosen Interface-Auswahl führen kann. Regressionstest mit
realen \r\n-getrennten hEX-Daten ergänzt.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HReLXMbmPvtQ23p1iWiJNW
Neuer Abschnitt erklärt den xcodebuild-test-Hang (amfid/syspolicyd
Gatekeeper-Netzwerk-Check bei ad-hoc-signierten Binaries) als
Tooling-Eigenheit, nicht Code-Bug, und wie man ihn umgeht
(build-for-testing zum Compile-Check, echte Verifikation über Xcode).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HReLXMbmPvtQ23p1iWiJNW
Standardmäßig aus (Toggle wie VLAN) -- höchstes Risiko aller bisherigen
Schritte, falsche Regeln können Fernzugriff kappen. Preset ist
Mikrotiks eigener Standard-Ansatz (unverändert seit Jahren in
RouterOS-Werkskonfigurationen): NAT/Masquerade auf WAN, established/
related erlauben, invalid verwerfen, unaufgeforderte WAN-Verbindungen
zu LAN-Geräten blocken (außer explizitem Port-Forward via
connection-nat-state=!dstnat).
Jede neue Regel bekommt ein place-before mit aufsteigendem Index,
damit sie vor eventuell schon vorhandenen Regeln des Routers landet --
sonst könnte eine bereits vorhandene "alles blocken"-Regel unsere
neuen Regeln wirkungslos machen. NAT und Filter sind getrennte,
unabhängig nummerierte RouterOS-Listen.
Vor dem Anwenden zeigt der Schritt die Anzahl bereits vorhandener
Filter-/NAT-Regeln (neuer fetchFirewallRuleCounts()-Aufruf in
RouterOSTransport/RestTransport/SSHTransport/ConnectionService) --
Transparenz, bevor auf einem möglicherweise schon konfigurierten
Router weitere Regeln landen. Nutzer-Entscheidung, extra Lese-Aufruf
in Kauf zu nehmen statt nur Warntext.
Build + Test-Compile (build-for-testing) sind grün. Der eigentliche
Testlauf (xcodebuild test) hängt aktuell an einem macOS-Gatekeeper-
Netzwerk-Check für ad-hoc-signierte Binaries (amfid: "adhoc signed or
signed by an unknown certificate chain", GK performScan über
syspolicyd) -- kein Code-Bug, tritt nur bei CLI-Testläufen auf, nicht
beim normalen Xcode-Cmd+R-Weg. Nutzer verifiziert M6 deshalb direkt in
Xcode.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HReLXMbmPvtQ23p1iWiJNW
Testgerät hat keinen WLAN-Chip -- korrekt erkannt, Konfiguration lief
ohne WLAN-Befehle sauber durch. Der eigentliche SSID/Passwort-.set-Pfad
bleibt ungetestet, dafür fehlt ein Gerät mit echtem WLAN-Chip.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HReLXMbmPvtQ23p1iWiJNW
RouterOSCommand unterstützt jetzt neben .add auch .set (bestehenden
Eintrag ändern statt neuen anzulegen) — nötig, weil WLAN-Interfaces
schon vor jeder Konfiguration existieren. SSH löst das per CLI-eigenem
"set [find field=value] ..." inline auf; REST hat dafür keine
Entsprechung und muss den Eintrag erst per GET suchen (matchField/
matchValue), seine .id auslesen, dann PATCH auf restPath/<id> senden
(RestTransport.findItemID). Mit dem Nutzer abgestimmte Entscheidung
gegen die einfachere "WLAN nur über SSH"-Variante.
WifiNetworkConfig: pro erkanntem Legacy-Wireless-Interface
(/interface wireless, type=wlan) eine SSID/Passwort-Konfiguration,
Sicherheitsprofil (WPA2) wird zuerst angelegt, dann per set mit dem
Interface verknüpft. Geräte ohne WLAN zeigen einen Hinweistext statt
des Formulars (User-Anforderung: muss berücksichtigt werden). Geräte
mit dem neueren "wifi"-Treiber (type=wifi, wifiwave2/802.11ax) werden
erkannt, aber bewusst nicht unterstützt -- anderes Menü, eigener
Umbau nötig, dazu Hinweistext.
cliPath wurde in allen bisherigen Command-Buildern (Wan/Lan/Vlan) zu
menuPath + .add migriert, da RouterOSCommand jetzt operation-basiert
ist statt den Aktionswort im Pfad-String zu verstecken.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HReLXMbmPvtQ23p1iWiJNW
Lesbare Nacherzählung des Gesprächsverlaufs (Planung, M1-M4, komplette
Live-Debugging-Kette gegen echtes Testgerät). Kein Rohtranskript --
das interne Session-Log liegt als ~8,7 MB JSONL vor und ist fürs Repo
nicht sinnvoll.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HReLXMbmPvtQ23p1iWiJNW
Fasst Stand nach M1-M4 zusammen: Aufbau, alle Live-Test-Fixes gegen
echtes Testgerät (SwiftUI-Observation, NIOSSHError/CommandFailed-
Fehlertexte, SSH-Algorithmen, Picker-Defaults), bewusste Scope-
Entscheidungen (VLAN ohne Port-Tagging) und offene Punkte für M5-M7.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HReLXMbmPvtQ23p1iWiJNW
Neuer optionaler Schritt im Einrichten-Wizard, standardmäßig
übersprungen (Toggle): legt ein VLAN-Interface auf einem gewählten
Basis-Anschluss an, mit eigenem IP-Bereich und eigenem DHCP-Server —
z.B. für Gäste/IoT. Bewusst ohne Port-Zuweisung (Access/Trunk): das
bräuchte RouterOS Bridge-VLAN-Filtering mit "set"-Operationen auf
bestehende Einträge, die unser bisheriges reines "add"-Befehlsmodell
(identisch für REST+SSH) nicht abdeckt. Wer ein VLAN auf einem
bestimmten Switch-Port braucht, muss den Trunk weiterhin manuell
einrichten. Entscheidung mit Nutzer abgestimmt.
DHCP-Befehlsbau (Adresse+Pool+Server+Netzwerk) aus dem LAN-Schritt in
DhcpServerCommandBuilder extrahiert, da jetzt zweimal identisch
gebraucht (LAN direkt, VLAN pro Eintrag).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HReLXMbmPvtQ23p1iWiJNW
wanConfig/lanConfig starteten mit festen Platzhaltern ("ether1"/
"bridge"), die nicht zwangsläufig zu den tatsächlichen Interfaces des
verbundenen Geräts passen -> SwiftUI-Picker meldete ungültige
Selection. prepareDefaults() korrigiert jetzt beide (vorher nur WAN)
anhand eines allgemeinen "ist der Wert überhaupt in der Liste"-Checks
statt nur "ist es noch der Platzhalter", und läuft bereits im init
statt erst bei onAppear, damit der erste Render schon die echten
Werte zeigt.
Aufgefallen beim Live-Test gegen echtes Testgerät (Konsolenwarnung
"the selection ... is invalid and does not have an associated tag").
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HReLXMbmPvtQ23p1iWiJNW
Citadels executeCommand() verwirft die gesammelte Kommandoausgabe,
sobald der Exit-Code ungleich 0 ist -- genau der Text, den RouterOS
bei einem fehlerhaften Befehl zurückgibt (z.B. falscher Parameter,
Interface existiert nicht). Eigene Sammlung über
executeCommandStream() behält die Ausgabe bis zum Fehlschlag und
hängt sie an die Fehlermeldung an.
Aufgefallen beim ersten Schreibtest (M3 "Einrichten"-Tab) gegen
echtes Testgerät: Fehler kam nur als nutzloses
"Citadel.SSHClient.CommandFailed error 1" durch.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HReLXMbmPvtQ23p1iWiJNW
RouterOS' SSH-Server bietet typischerweise nur ältere Algorithmen an
(diffie-hellman-group14-sha1, RSA-Hostkeys), die in Citadels
Standard-Algorithmenliste fehlen -> keyExchangeNegotiationFailure beim
Verbindungstest gegen echtes Gerät. Citadels vorgefertigtes
SSHAlgorithms.all-Preset aktiviert diese Legacy-Algorithmen.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HReLXMbmPvtQ23p1iWiJNW
NIOSSHError.localizedDescription bridged auf NSError und liefert nur
"The operation couldn't be completed. (NIOSSH.NIOSSHError error 1.)" —
der eigentliche Fehlertyp/die Diagnostik steckt in der internen
CustomStringConvertible-Beschreibung. String(describing:) statt
.localizedDescription verwenden, um den echten Grund (z.B. Algorithmus-
Aushandlung, Host-Key, Auth) sichtbar zu machen.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HReLXMbmPvtQ23p1iWiJNW
connectionService war nur über viewModel.connectionService erreichbar,
ein zwei Ebenen tief verschachteltes ObservableObject. SwiftUI abonniert
automatisch nur Objekte, die eine View direkt per @StateObject/
@ObservedObject hält — nicht transitiv erreichte. Dadurch blieb die
Oberfläche beim echten Verbindungstest (physisches Mikrotik-Gerät)
komplett unverändert hängen, obwohl die Verbindung im Hintergrund
korrekt lief oder fehlschlug.
Fix: ConnectView hält connectionService jetzt zusätzlich selbst als
@ObservedObject, direkt von der App-Ebene durchgereicht.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HReLXMbmPvtQ23p1iWiJNW
Neuer "Einrichten"-Tab führt durch Internet-Anschluss (DHCP/statisch/
PPPoE) und Heimnetzwerk+DHCP-Server, zeigt vor dem Anwenden eine
Klartext-Übersicht (optional mit den exakten RouterOS-Befehlen) und
erstellt automatisch eine Sicherung, bevor Änderungen geschrieben
werden. Änderungen laufen über beide Transporte: CLI-Zeile für SSH,
JSON-POST für REST — beide aus einem gemeinsamen RouterOSCommand
gebaut. RouterOS-CLI-Syntax ist Standard und langjährig stabil, aber
nicht gegen ein echtes Gerät verifiziert; deshalb die Detailanzeige
im Übersichtsschritt vor dem Anwenden.
ConnectionService ist jetzt der einzige App-weite Zustand (ersetzt
SessionStore) und wird explizit an alle drei Tabs durchgereicht.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HReLXMbmPvtQ23p1iWiJNW
Config-Export (/export terse) über eine dedizierte SSH-Verbindung,
unabhängig vom aktiven Live-Transport (REST oder SSH) — RouterOS' REST-API
hat keinen generischen Export-Endpunkt. Lokale Sicherungen unter
~/Library/Application Support/RouterOSAssistant/Backups/. Neuer
"Sicherungen"-Tab mit manuellem "Jetzt sichern"-Button, SessionStore
teilt die Zugangsdaten der aktiven Verbindung zwischen den Tabs.
Der bestehende Connect-Schritt dient bereits als reiner Lese-Modus
(nur GET-Aufrufe) — ein zusätzlicher Dry-Run-Schalter entfällt, da es
vor M3 noch keine Schreibpfade gibt.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HReLXMbmPvtQ23p1iWiJNW
XcodeGen-basiertes SwiftUI-Projekt für den RouterOS-Interview-Assistenten.
Erster Wizard-Schritt: Verbindung zu Mikrotik-Geräten per REST-API
(RouterOS >=7.1) mit SSH-CLI-Fallback für ältere Firmware, Zertifikats-
TOFU-Bestätigung, Zugangsdaten im Keychain. Unit-Tests für CLI-Parser
und Fallback-Logik.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HReLXMbmPvtQ23p1iWiJNW