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
@@ -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)
}
}
}