Wizard: idempotenter WAN-Apply fuer DHCP-Client/PPPoE (Bug 21)

Wizard erneut auf einem Interface durchlaufen, das schon einen
DHCP-Client hat (hier: aus einer zurueckgespielten Sicherung), scheiterte
live mit "failure: dhcp-client on that interface already exists" -
WanConfig.buildCommands() erzeugt fuer DHCP-Client/PPPoE immer .add, ohne
vorher zu pruefen, ob auf dem Interface schon einer existiert.

Fix: SetupViewModel.applyIdempotently faengt einen .add-Fehlschlag auf
/ip dhcp-client bzw. /interface pppoe-client ab und wiederholt ihn als
.set (nach "interface" gematcht) - bewusst nur fuer diese zwei Menues
mit "maximal ein Eintrag pro Interface"-Semantik, nicht generell fuer
jedes .add (z.B. /ip address erlaubt legitim mehrere Adressen pro
Interface).

Gefunden beim Versuch, M8 (Netzwerk-Isolation) live durchzutesten -
dieser Test selbst ist noch nicht abgeschlossen, naechste Session dort
weitermachen (siehe HANDOFF.md Naechste Schritte Punkt 1).

HANDOFF.md/CHATLOG.md aktualisiert: Bug 21, Sessionende-Stand.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CTgRxJTzaQwaRkngbaE1GJ
This commit is contained in:
Kay
2026-09-14 20:43:56 +02:00
co-authored by Claude Sonnet 5
parent e72da84843
commit 0e8d910352
3 changed files with 87 additions and 6 deletions
+20
View File
@@ -676,3 +676,23 @@ Test, WLAN/Bonding/PPPoE-Live-Tests).
- "ja, committen und beides aktualisieren" → dieser Eintrag, HANDOFF.md
(M14, Bug 20, Design-Durchgang, Dark-Mode-Bestätigung) aktualisiert,
Commit erstellt.
## Session: M8-Hardware-Test begonnen (nicht abgeschlossen)
- "was steht als nächstes auf dem Plan?" → HANDOFF-Liste zusammengefasst.
"fangen wir bei 4 an" (M8 Netzwerk-Isolation) → per `AskUserQuestion`
geklärt: voller Wizard-Durchlauf statt nur gezieltem Firewall-Test,
kombiniert damit Punkt 1 (kompletter Wizard-Durchlauf) mit.
- Unterwegs zwei praktische RouterOS-Fragen beantwortet: ether5 aus der
Bridge entfernen (`/interface bridge port remove [find
interface=ether5]` bzw. Experte-Tab), dann "Unerwartete Antwort vom
Router: failure: dhcp-client on that interface already exists" beim
WAN-Schritt — Ursache: Wizard nutzt immer `.add`, aber auf dem
Interface existierte aus der zurückgespielten Sicherung schon ein
DHCP-Client. "fixe es gleich" → Bug 21, `SetupViewModel.
applyIdempotently` (Fallback auf `.set` bei `.add`-Fehlschlag auf
DHCP-Client/PPPoE), deployt.
- "ok, alles speichern, genug für heute" → dieser Eintrag, HANDOFF.md
(Bug 21, Nächste-Schritte Punkt 1 als "in Arbeit" markiert — der
eigentliche M8-Isolationstest ist noch nicht zu Ende geführt, nächste
Session dort fortsetzen) aktualisiert, Commit erstellt. Sitzung beendet.
+34 -5
View File
@@ -56,6 +56,17 @@ Farbwerte, alles adaptive System-Farben) — vom Nutzer live bestätigt.
Release-Build unter `/Applications/RouterOS Assistant.app` ist auf
aktuellem Stand.
**Zum Sessionende:** M8-Hardware-Test (Netzwerk-Isolation) begonnen —
voller Wizard-Durchlauf über den Experte-Modus, WAN neu einrichten.
Dabei Bug 21 gefunden: der Wizard-Apply nutzte für WAN-DHCP-Client/PPPoE
immer `.add`, was auf einem bereits konfigurierten Interface (hier: aus
der zurückgespielten Sicherung) mit "failure: dhcp-client on that
interface already exists" scheiterte. Fix: `SetupViewModel.
applyIdempotently` versucht bei `.add` auf `/ip dhcp-client` oder
`/interface pppoe-client` nach einem Fehlschlag automatisch `.set`
(nach "interface" gematcht) — deployt, **aber der eigentliche
M8-Isolationstest selbst ist noch nicht zu Ende geführt/verifiziert**,
das ist der erste Schritt für die nächste Session.
## Ziel
Native macOS-App (SwiftUI), die Laien per geführtem Interview-Wizard durch
@@ -378,6 +389,19 @@ erreichen (siehe Bug 1 unten).
Singleton-Menü, nicht nur `/system routerboard` — der generische
Lese-Pfad wird von Übersicht-, Geräte- und Experte-Tab gemeinsam
genutzt.
21. **Wizard-WAN-Apply war nicht idempotent** — Wizard erneut auf einem
Interface durchlaufen, das schon einen DHCP-Client hat (hier: aus
einer zurückgespielten Sicherung), scheiterte live mit "failure:
dhcp-client on that interface already exists". `WanConfig.
buildCommands()` erzeugt für DHCP-Client/PPPoE immer `.add`, ohne
vorher zu prüfen, ob auf dem Interface schon einer existiert. Fix:
`SetupViewModel.applyIdempotently` fängt einen `.add`-Fehlschlag auf
`/ip dhcp-client`/`/interface pppoe-client` ab und wiederholt ihn als
`.set` (nach "interface" gematcht) — bewusst nur für diese zwei
Menüs mit "maximal ein Eintrag pro Interface"-Semantik, nicht generell
für jedes `.add` (z.B. `/ip address` erlaubt legitim mehrere
Adressen pro Interface). **Noch nicht erneut live getestet**, siehe
Nächste Schritte.
**Lehren:** Citadel/NIOSSH-Fehler immer mit `String(describing:)` loggen,
nie `.localizedDescription`. Jede View, die ein ObservableObject aus einem
@@ -741,11 +765,16 @@ beim Ändern die RouterOS-Suffix-Form zurück) — angewendet auf DHCP-Server
## Nächste Schritte
1. Den kompletten Wizard einmal neu durchlaufen (WAN/LAN/VLAN/WLAN/
Firewall) auf dem werksresetteten hEX und dabei das Firewall-Ergebnis
mit korrektem WAN-Port per `/ip firewall filter print` /
`/ip firewall nat print` kontrollieren (bisher nur die Interface-
Auswahl nach dem Parser-Fix bestätigt, kein erneuter Apply).
1. **In Arbeit, hier weitermachen:** kompletter Wizard-Durchlauf
(WAN/LAN mit zwei Interfaces ether2+ether3, eins isoliert/Firewall)
über den Experte-Modus — kombiniert M8-Hardware-Test (Netzwerk-
Isolation) mit diesem Punkt. Wurde in dieser Session begonnen, brach
beim WAN-Schritt mit Bug 21 ab (jetzt gefixt, deployt, aber
**ungetestet**). Nach erneutem Durchlauf: Firewall-Ergebnis mit
korrektem WAN-Port UND die Isolations-Regeln per
`/ip firewall filter print` kontrollieren, plus praktisch testen
(iMac ↔ Laptop sollten sich nicht erreichen, Internet für beide
schon).
2. WLAN (M5) an einem Gerät mit echtem WLAN-Chip nachholen.
3. Rest von M7: Fehlerzustände/Politur, REST-Schreibtest an einem Gerät
mit aktivem `www-ssl`.
@@ -213,7 +213,7 @@ final class SetupViewModel: ObservableObject {
for command in plannedCommands {
applyLog.append(command.summary)
try await connectionService.apply(command)
try await applyIdempotently(command)
}
didApplySuccessfully = true
@@ -223,4 +223,36 @@ final class SetupViewModel: ObservableObject {
isApplying = false
}
}
/// RouterOS allows at most one item per interface on some menus (a DHCP client, a PPPoE
/// client) re-running the wizard's WAN step on an interface that already has one makes the
/// wizard's own `.add` fail live-confirmed: "failure: dhcp-client on that interface already
/// exists". Rather than hard-fail the whole apply here, retry once as a `.set` matched by
/// "interface" instead reconfigures the existing entry in place, which is what re-running
/// the wizard on the same interface should mean anyway. Scoped to exactly these two
/// known-unique-per-interface menus, not applied generally to every `.add` other menus
/// (e.g. "/ip address") legitimately allow multiple items per interface, where silently
/// converting a would-be-duplicate `.add` into a `.set` would be wrong, not helpful.
private static let interfaceUniqueMenuPaths: Set<String> = ["/ip dhcp-client", "/interface pppoe-client"]
private func applyIdempotently(_ command: RouterOSCommand) async throws {
do {
try await connectionService.apply(command)
} catch {
guard case .add = command.operation,
Self.interfaceUniqueMenuPaths.contains(command.menuPath),
let interfaceName = command.arguments["interface"] else {
throw error
}
let retryCommand = RouterOSCommand.set(
menuPath: command.menuPath,
restPath: command.restPath,
matchField: "interface",
matchValue: interfaceName,
arguments: command.arguments,
summary: command.summary
)
try await connectionService.apply(retryCommand)
}
}
}